├── Fit Vein Tests ├── Fit_Vein_Tests.swift ├── FunctionsTests.swift ├── HealthStatTests.swift ├── IntervalWorkoutTests.swift └── PostTests.swift ├── Fit Vein UI Tests ├── Fit_Vein_UI_Tests.swift └── Fit_Vein_UI_TestsLaunchTests.swift ├── Fit Vein.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── vader.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Fit Vein ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── Contents.json │ ├── SignInBackgroundImage.imageset │ │ ├── Contents.json │ │ └── gym.jpg │ └── SignUpBackgroundImage.imageset │ │ ├── Contents.json │ │ └── gym.jpg ├── Constants&Utils │ ├── Enums.swift │ ├── Errors.swift │ ├── Functions.swift │ ├── HealthKitRepository.swift │ ├── NetworkManager.swift │ └── SwiftUIImagePicker.swift ├── ContentView.swift ├── FIrebase Utils │ ├── FirebaseStorageManager.swift │ ├── FirestoreManager.swift │ ├── RCValues.swift │ └── SessionStore.swift ├── Fit Vein.entitlements ├── Fit_VeinApp.swift ├── Images │ ├── FitVeinIconDark.png │ ├── FitVeinIconLight.png │ ├── blank-profile-hi.png │ ├── gym.jpg │ └── sprint2.png ├── LoggedUserView.swift ├── Lottie │ ├── 20022-character-squat-animation.json │ ├── 43323-men-doing-rope-exercise.json │ ├── 70772-smiley-loader.json │ ├── avocadoWorkout.json │ ├── baboonExercising.json │ ├── countdown.json │ ├── countdown2.json │ ├── distance.json │ ├── downArrows.json │ ├── dumbleLoading.json │ ├── flame.json │ ├── heartRate.json │ ├── hello.json │ ├── levelUp.json │ ├── loading.json │ ├── noInternetConnection.json │ ├── runnerAddWorkout.json │ ├── shoesWalking.json │ ├── skeleton.json │ ├── success2.json │ ├── time.json │ ├── time2.json │ └── wrongData.json ├── LottieView.swift ├── Medals │ ├── medal-2.png │ ├── medal-3.png │ ├── medal-4.png │ ├── medal.png │ ├── medalFirstComment.png │ ├── medalFirstLike.png │ ├── medalFirstOwnWorkout.png │ ├── medalFirstPost.png │ ├── medalFirstWorkout.png │ ├── medalSecondLevel.png │ └── medalThirdLevel.png ├── Models │ ├── HealthStat.swift │ ├── IntervalWorkout.swift │ ├── Post.swift │ └── Profile.swift ├── Onboarding Photos │ ├── AddWorkoutView.png │ ├── FinishedWorkoutView.png │ ├── HealthTabView.png │ ├── HomeTabView.png │ ├── MedalView.png │ ├── PostCommentsView.png │ ├── ProfileView.png │ ├── SearchFriendsView.png │ ├── SettingsView.png │ ├── WorkoutTimerView.png │ ├── WorkoutsTabView.png │ ├── WorkoutsTabViewList.png │ └── WorkoutsView.png ├── OnboardingView.swift ├── PreLaunchView.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── ViewModels │ ├── HealthKitViewModel.swift │ ├── HomeViewModel.swift │ ├── MedalsViewModel.swift │ ├── ProfileViewModel.swift │ ├── SignInViewModel.swift │ ├── SignUpViewModel.swift │ └── WorkoutViewModel.swift ├── Views │ ├── AuthenticationViews │ │ ├── SignInView.swift │ │ └── SignUpView.swift │ ├── HelpViews │ │ ├── CustomTextField.swift │ │ ├── HomeTabFetchingView.swift │ │ ├── ProfileTabFetchingView.swift │ │ └── RingProgressViewStyle.swift │ ├── HomeTabViews │ │ ├── CommentsViews │ │ │ ├── HomeTabCommentsView.swift │ │ │ ├── HomeTabCommentsViewPostView.swift │ │ │ └── PostCommentsView.swift │ │ ├── HomeView.swift │ │ ├── NotificationsView.swift │ │ ├── PostsViews │ │ │ ├── AddPostView.swift │ │ │ ├── EditPostView.swift │ │ │ ├── HomeTabSubViewPostDetailsView.swift │ │ │ ├── HomeTabSubViewPostsView.swift │ │ │ └── HomeTabSubViewShareView.swift │ │ └── SearchFriendsView.swift │ ├── ProfileTabViews │ │ ├── HealthTabView.swift │ │ ├── ProfileView.swift │ │ ├── SettingsView.swift │ │ └── WorkoutTabView.swift │ └── WorkoutTabViews │ │ ├── FinishedWorkoutView.swift │ │ ├── SingleWorkoutWindowView.swift │ │ ├── WorkoutTimerView.swift │ │ └── WorkoutView.swift ├── en.lproj │ └── Localizable.strings └── pl.lproj │ └── Localizable.strings ├── LICENSE └── README.md /Fit Vein Tests/Fit_Vein_Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fit_Vein_Tests.swift 3 | // Fit Vein Tests 4 | // 5 | // Created by Łukasz Janiszewski on 04/01/2022. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | @testable import Fit_Vein 11 | 12 | class Fit_Vein_Tests: XCTestCase { 13 | 14 | // override func setUpWithError() throws { 15 | // // Put setup code here. This method is called before the invocation of each test method in the class. 16 | // } 17 | // 18 | // override func tearDownWithError() throws { 19 | // // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | // } 21 | // 22 | // func testExample() throws { 23 | // // This is an example of a functional test case. 24 | // // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | // // Any test you write for XCTest can be annotated as throws and async. 26 | // // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 27 | // // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 28 | // } 29 | // 30 | // func testPerformanceExample() throws { 31 | // // This is an example of a performance test case. 32 | // measure { 33 | // // Put the code you want to measure the time of here. 34 | // } 35 | // } 36 | } 37 | -------------------------------------------------------------------------------- /Fit Vein Tests/FunctionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionsTests.swift 3 | // Fit Vein Tests 4 | // 5 | // Created by Łukasz Janiszewski on 04/01/2022. 6 | // 7 | 8 | import XCTest 9 | @testable import Fit_Vein 10 | 11 | class FunctionsTests: XCTestCase { 12 | 13 | let dateFormatter = DateFormatter() 14 | 15 | func testYearsBetweenDate() { 16 | dateFormatter.dateFormat = "dd/MM/yy" 17 | let startDate = dateFormatter.date(from: "01/02/2016")! 18 | let endDate = dateFormatter.date(from: "02/02/2016")! 19 | 20 | let result = yearsBetweenDate(startDate: startDate, endDate: endDate) 21 | XCTAssertEqual(result, 0) 22 | } 23 | 24 | func testGetWorkoutsDivider() { 25 | var workoutsCount = 11 26 | let result = getWorkoutsDivider(workoutsCount: workoutsCount) 27 | XCTAssertEqual(result, 1) 28 | 29 | workoutsCount = 1 30 | XCTAssertEqual(result, 1) 31 | } 32 | 33 | func testGetShortDate() { 34 | let stringDate = "2022-01-04 18:04:41 +0000" 35 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZZ" 36 | let date = dateFormatter.date(from: stringDate)! 37 | let result = getShortDate(longDate: date) 38 | XCTAssertEqual(result, "4 stycznia 2022 o 7:04 PM") 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Fit Vein Tests/HealthStatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthStatTests.swift 3 | // Fit Vein Tests 4 | // 5 | // Created by Łukasz Janiszewski on 04/01/2022. 6 | // 7 | 8 | import XCTest 9 | @testable import Fit_Vein 10 | 11 | class HealthStatTests: XCTestCase { 12 | 13 | var healthStat: HealthStat! 14 | let dateFormatter = DateFormatter() 15 | var date: Date = Date() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | dateFormatter.dateFormat = "dd/MM/yy" 20 | date = dateFormatter.date(from: "01/02/2016")! 21 | healthStat = HealthStat(stat: nil, date: date) 22 | } 23 | 24 | override func tearDown() { 25 | super.tearDown() 26 | healthStat = nil 27 | } 28 | 29 | func testHealthStatIDInitialization() { 30 | XCTAssertNil(healthStat.stat) 31 | XCTAssertEqual(healthStat.date, date) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Fit Vein Tests/IntervalWorkoutTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntervalWorkoutTests.swift 3 | // Fit Vein Tests 4 | // 5 | // Created by Łukasz Janiszewski on 04/01/2022. 6 | // 7 | 8 | import XCTest 9 | @testable import Fit_Vein 10 | 11 | class IntervalWorkoutTests: XCTestCase { 12 | 13 | var intervalWorkout: IntervalWorkout! 14 | let dateFormatter = DateFormatter() 15 | var date: Date = Date() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | dateFormatter.dateFormat = "dd/MM/yy" 20 | date = dateFormatter.date(from: "01/02/2016")! 21 | intervalWorkout = IntervalWorkout(forPreviews: true, id: "id", usersID: "usersID", type: "type", date: date, isFinished: true, calories: 32, series: 5, workTime: 30, restTime: 15, completedDuration: 10, completedSeries: 0) 22 | } 23 | 24 | override func tearDown() { 25 | super.tearDown() 26 | intervalWorkout = nil 27 | } 28 | 29 | func testPostSecondInit() { 30 | XCTAssertEqual(intervalWorkout.id, "id") 31 | XCTAssertEqual(intervalWorkout.usersID, "usersID") 32 | XCTAssertEqual(intervalWorkout.type, "type") 33 | XCTAssertEqual(intervalWorkout.date, date) 34 | XCTAssertEqual(intervalWorkout.isFinished, true) 35 | XCTAssertEqual(intervalWorkout.calories, 32) 36 | XCTAssertEqual(intervalWorkout.series, 5) 37 | XCTAssertEqual(intervalWorkout.workTime, 30) 38 | XCTAssertEqual(intervalWorkout.restTime, 15) 39 | XCTAssertEqual(intervalWorkout.completedDuration, 10) 40 | XCTAssertEqual(intervalWorkout.completedSeries, 0) 41 | } 42 | 43 | func testSetDataOnEnd() { 44 | intervalWorkout.setDataOnEnd(calories: 200, completedDuration: 40, completedSeries: 2) 45 | XCTAssertEqual(intervalWorkout.isFinished, true) 46 | XCTAssertEqual(intervalWorkout.calories, 200) 47 | XCTAssertEqual(intervalWorkout.completedDuration, 40) 48 | XCTAssertEqual(intervalWorkout.completedSeries, 2) 49 | } 50 | 51 | func testSetUsersID() { 52 | intervalWorkout.setUsersID(usersID: "usersID2") 53 | XCTAssertEqual(intervalWorkout.usersID, "usersID2") 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Fit Vein Tests/PostTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostTests.swift 3 | // Fit Vein Tests 4 | // 5 | // Created by Łukasz Janiszewski on 04/01/2022. 6 | // 7 | 8 | import XCTest 9 | @testable import Fit_Vein 10 | 11 | class PostTests: XCTestCase { 12 | var post: Post! 13 | var post2: Post! 14 | 15 | var comment: Comment! 16 | var comment2: Comment! 17 | 18 | override func setUp() { 19 | super.setUp() 20 | post = Post(authorID: "authorID", authorFirstName: "authorFirstName", authorUsername: "authorUsername", authorProfilePictureURL: "authorProfilePictureURL", text: "text") 21 | post2 = Post(id: "id", authorID: "authorID", authorFirstName: "authorFirstName", authorUsername: "authorUsername", authorProfilePictureURL: "authorProfilePictureURL", addDate: Date(), text: "text", reactionsUsersIDs: nil, commentedUsersIDs: nil, comments: nil) 22 | 23 | comment = Comment(authorID: "authorID", postID: "postID", authorFirstName: "authorFirstName", authorUsername: "authorUsername", authorProfilePictureURL: "authorProfilePictureURL", text: "text") 24 | comment2 = Comment(id: "id", authorID: "authorID", postID: "postID", authorFirstName: "authorFirstName", authorUsername: "authorUsername", authorProfilePictureURL: "authorProfilePictureURL", addDate: Date(), text: "text", reactionsUsersIDs: nil) 25 | } 26 | 27 | override func tearDown() { 28 | super.tearDown() 29 | post = nil 30 | post2 = nil 31 | 32 | comment = nil 33 | comment2 = nil 34 | } 35 | 36 | func testPostFirstInit() { 37 | XCTAssertEqual(post.authorID, "authorID") 38 | XCTAssertEqual(post.authorFirstName, "authorFirstName") 39 | XCTAssertEqual(post.authorUsername, "authorUsername") 40 | XCTAssertEqual(post.authorProfilePictureURL, "authorProfilePictureURL") 41 | XCTAssertEqual(post.text, "text") 42 | } 43 | 44 | func testPostSecondInit() { 45 | XCTAssertEqual(post2.id, "id") 46 | XCTAssertEqual(post2.authorID, "authorID") 47 | XCTAssertEqual(post2.authorFirstName, "authorFirstName") 48 | XCTAssertEqual(post2.authorUsername, "authorUsername") 49 | XCTAssertEqual(post2.authorProfilePictureURL, "authorProfilePictureURL") 50 | XCTAssertEqual(post2.text, "text") 51 | XCTAssertNil(post2.reactionsUsersIDs) 52 | XCTAssertNil(post2.commentedUsersIDs) 53 | XCTAssertNil(post2.comments) 54 | } 55 | 56 | func testCommentFirstInit() { 57 | XCTAssertEqual(comment.authorID, "authorID") 58 | XCTAssertEqual(comment.postID, "postID") 59 | XCTAssertEqual(comment.authorFirstName, "authorFirstName") 60 | XCTAssertEqual(comment.authorUsername, "authorUsername") 61 | XCTAssertEqual(comment.authorProfilePictureURL, "authorProfilePictureURL") 62 | XCTAssertEqual(comment.text, "text") 63 | } 64 | 65 | func testCommentSecondInit() { 66 | XCTAssertEqual(comment2.id, "id") 67 | XCTAssertEqual(comment2.authorID, "authorID") 68 | XCTAssertEqual(comment.postID, "postID") 69 | XCTAssertEqual(comment2.authorFirstName, "authorFirstName") 70 | XCTAssertEqual(comment2.authorUsername, "authorUsername") 71 | XCTAssertEqual(comment2.authorProfilePictureURL, "authorProfilePictureURL") 72 | XCTAssertEqual(comment2.text, "text") 73 | XCTAssertNil(comment2.reactionsUsersIDs) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Fit Vein UI Tests/Fit_Vein_UI_TestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fit_Vein_UI_TestsLaunchTests.swift 3 | // Fit Vein UI Tests 4 | // 5 | // Created by Łukasz Janiszewski on 04/01/2022. 6 | // 7 | 8 | import XCTest 9 | 10 | class Fit_Vein_UI_TestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Fit Vein.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fit Vein.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fit Vein.xcodeproj/xcuserdata/vader.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Fit Vein.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 25 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 0AC4DD802715A9E4003D5FED 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "ios", 6 | "reference" : "systemGreenColor" 7 | }, 8 | "idiom" : "universal" 9 | }, 10 | { 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "light" 15 | } 16 | ], 17 | "color" : { 18 | "platform" : "ios", 19 | "reference" : "systemGreenColor" 20 | }, 21 | "idiom" : "universal" 22 | }, 23 | { 24 | "appearances" : [ 25 | { 26 | "appearance" : "luminosity", 27 | "value" : "dark" 28 | } 29 | ], 30 | "color" : { 31 | "platform" : "ios", 32 | "reference" : "systemGreenColor" 33 | }, 34 | "idiom" : "universal" 35 | }, 36 | { 37 | "color" : { 38 | "platform" : "ios", 39 | "reference" : "systemGreenColor" 40 | }, 41 | "idiom" : "iphone" 42 | }, 43 | { 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "light" 48 | } 49 | ], 50 | "color" : { 51 | "platform" : "ios", 52 | "reference" : "systemGreenColor" 53 | }, 54 | "idiom" : "iphone" 55 | }, 56 | { 57 | "appearances" : [ 58 | { 59 | "appearance" : "luminosity", 60 | "value" : "dark" 61 | } 62 | ], 63 | "color" : { 64 | "platform" : "ios", 65 | "reference" : "systemGreenColor" 66 | }, 67 | "idiom" : "iphone" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/SignInBackgroundImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "gym.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/SignInBackgroundImage.imageset/gym.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/SignInBackgroundImage.imageset/gym.jpg -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/SignUpBackgroundImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "gym.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fit Vein/Assets.xcassets/SignUpBackgroundImage.imageset/gym.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Assets.xcassets/SignUpBackgroundImage.imageset/gym.jpg -------------------------------------------------------------------------------- /Fit Vein/Constants&Utils/Enums.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Enums.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 12/10/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Country: String, CaseIterable, Identifiable { 11 | case poland 12 | 13 | public var id: String { self.rawValue } 14 | } 15 | 16 | public enum Language: String, CaseIterable, Identifiable { 17 | case english 18 | case polish 19 | 20 | public var id: String { self.rawValue } 21 | } 22 | -------------------------------------------------------------------------------- /Fit Vein/Constants&Utils/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 13/10/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AuthError: String, Error { 11 | case authFailed = "Failed to authenticate the user" 12 | case signOutFailed = "Failed to sign out" 13 | } 14 | 15 | enum DatabaseError: String, Error { 16 | case createUserDataFailed = "Failed to create user data in database" 17 | case fetchUserDataFailed = "Failed to fetch data from database" 18 | case deleteUserDataFailed = "Failed to delete user data from database" 19 | case editUserDataFailed = "Failed to edit user data in database" 20 | } 21 | -------------------------------------------------------------------------------- /Fit Vein/Constants&Utils/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 13/10/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public func yearsBetweenDate(startDate: Date, endDate: Date) -> Int { 12 | let calendar = Calendar.current 13 | let components = calendar.dateComponents([.year], from: startDate, to: endDate) 14 | return components.year! 15 | } 16 | 17 | public func getWorkoutsDivider(workoutsCount: Int) -> Int { 18 | var workoutsCountNumber = workoutsCount 19 | while workoutsCountNumber > 10 { 20 | workoutsCountNumber = workoutsCountNumber / 10 21 | } 22 | return workoutsCountNumber % 10 23 | } 24 | 25 | public func getShortDate(longDate: Date) -> String { 26 | let dateFormatter = DateFormatter() 27 | dateFormatter.dateStyle = .long 28 | dateFormatter.timeStyle = .short 29 | 30 | return dateFormatter.string(from: longDate) 31 | } 32 | 33 | public func getTextTimeFromDuration(duration: Int) -> Text { 34 | var secondsRemaining = 0 35 | var minutesRemaining = 0 36 | 37 | if duration >= 60 { 38 | minutesRemaining = Int(duration / 60) 39 | secondsRemaining = duration - (60 * minutesRemaining) 40 | } else { 41 | minutesRemaining = 0 42 | secondsRemaining = duration 43 | } 44 | 45 | if minutesRemaining < 10 { 46 | if secondsRemaining < 10 { 47 | return Text("0\(minutesRemaining):0\(secondsRemaining)") 48 | } else { 49 | return Text("0\(minutesRemaining):\(secondsRemaining)") 50 | } 51 | } else { 52 | if secondsRemaining < 10 { 53 | return Text("\(minutesRemaining):0\(secondsRemaining)") 54 | } else { 55 | return Text("\(minutesRemaining):\(secondsRemaining)") 56 | } 57 | } 58 | } 59 | 60 | public func firstDayOfWeek() -> Date { 61 | return Calendar(identifier: .iso8601).date(from: Calendar(identifier: .iso8601).dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date())) ?? Date() 62 | } 63 | -------------------------------------------------------------------------------- /Fit Vein/Constants&Utils/HealthKitRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitRepository.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 24/11/2021. 6 | // 7 | 8 | import Foundation 9 | import HealthKit 10 | 11 | final class HealthKitRepository { 12 | var healthStore: HKHealthStore? 13 | 14 | let allTypes = Set([ 15 | HKObjectType.quantityType(forIdentifier: .stepCount)!, 16 | HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!, 17 | HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning)!, 18 | HKObjectType.quantityType(forIdentifier: .appleExerciseTime)!, 19 | HKObjectType.quantityType(forIdentifier: .heartRate)! 20 | ]) 21 | 22 | var query: HKStatisticsCollectionQuery? 23 | 24 | init() { 25 | if HKHealthStore.isHealthDataAvailable() { 26 | healthStore = HKHealthStore() 27 | } 28 | } 29 | 30 | func requestAuthorization(completion: @escaping ((Bool) -> Void)) { 31 | guard let store = healthStore else { 32 | return 33 | } 34 | 35 | store.requestAuthorization(toShare: [], read: allTypes) { (success, error) in 36 | if let error = error { 37 | print("HealthKit authorization error: \(error)") 38 | } else { 39 | print("Health authorization success") 40 | completion(success) 41 | } 42 | } 43 | } 44 | 45 | func requestHealthStats(by category: String, completion: @escaping (([HealthStat]) -> Void)) { 46 | guard let store = healthStore, let type = HKObjectType.quantityType(forIdentifier: typeByCategory(category: category)) else { 47 | return 48 | } 49 | 50 | let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date()) ?? Date() 51 | let endDate = Date() 52 | let anchorDate = firstDayOfWeek() 53 | let dailyComponent = DateComponents(day: 1) 54 | 55 | var healthStats = [HealthStat]() 56 | 57 | let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate) 58 | 59 | query = HKStatisticsCollectionQuery(quantityType: type, quantitySamplePredicate: predicate, options: type == HKObjectType.quantityType(forIdentifier: typeByCategory(category: "heartRate")) ? .discreteAverage : .cumulativeSum, anchorDate: anchorDate, intervalComponents: dailyComponent) 60 | 61 | query?.initialResultsHandler = { (query, statistics, error) in 62 | if let error = error { 63 | print("Error in requestHealthStats functions in initialResultsHandler: \(error)") 64 | } else { 65 | statistics?.enumerateStatistics(from: startDate, to: endDate, with: { (stats, _) in 66 | if stats.quantityType == HKObjectType.quantityType(forIdentifier: .heartRate) { 67 | let stat = HealthStat(stat: stats.averageQuantity(), date: stats.startDate) 68 | healthStats.append(stat) 69 | } else { 70 | let stat = HealthStat(stat: stats.sumQuantity(), date: stats.startDate) 71 | healthStats.append(stat) 72 | } 73 | }) 74 | 75 | completion(healthStats) 76 | } 77 | } 78 | 79 | guard let query = query else { 80 | return 81 | } 82 | 83 | store.execute(query) 84 | } 85 | 86 | private func typeByCategory(category: String) -> HKQuantityTypeIdentifier { 87 | switch category { 88 | case "stepCount": 89 | return .stepCount 90 | case "activeEnergyBurned": 91 | return .activeEnergyBurned 92 | case "distanceWalkingRunning": 93 | return .distanceWalkingRunning 94 | case "appleExerciseTime": 95 | return .appleExerciseTime 96 | case "heartRate": 97 | return .heartRate 98 | default: 99 | return .stepCount 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Fit Vein/Constants&Utils/NetworkManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkManager.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 09/01/2022. 6 | // 7 | 8 | import Foundation 9 | import Network 10 | 11 | class NetworkManager: ObservableObject { 12 | let monitor = NWPathMonitor() 13 | let queue = DispatchQueue(label: "NetworkManager") 14 | @Published var isConnected = true 15 | 16 | init() { 17 | monitor.pathUpdateHandler = { path in 18 | DispatchQueue.main.async { 19 | self.isConnected = path.status == .satisfied 20 | } 21 | } 22 | 23 | monitor.start(queue: queue) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Fit Vein/Constants&Utils/SwiftUIImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIImagePicker.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 20/10/2021. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | struct ImagePicker: UIViewControllerRepresentable { 12 | 13 | var sourceType: UIImagePickerController.SourceType = .photoLibrary 14 | 15 | @Binding var selectedImage: UIImage 16 | @Environment(\.presentationMode) private var presentationMode 17 | 18 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { 19 | 20 | let imagePicker = UIImagePickerController() 21 | imagePicker.allowsEditing = true 22 | imagePicker.sourceType = sourceType 23 | imagePicker.delegate = context.coordinator 24 | 25 | return imagePicker 26 | } 27 | 28 | func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext) { 29 | 30 | } 31 | 32 | func makeCoordinator() -> Coordinator { 33 | Coordinator(self) 34 | } 35 | 36 | 37 | final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 38 | 39 | var parent: ImagePicker 40 | 41 | init(_ parent: ImagePicker) { 42 | self.parent = parent 43 | } 44 | 45 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 46 | if let image = info[.editedImage] as? UIImage { 47 | parent.selectedImage = image 48 | } else if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { 49 | parent.selectedImage = image 50 | } 51 | 52 | parent.presentationMode.wrappedValue.dismiss() 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Fit Vein/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 12/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | import LocalAuthentication 10 | 11 | struct ContentView: View { 12 | @EnvironmentObject private var sessionStore: SessionStore 13 | @Environment(\.colorScheme) var colorScheme 14 | @AppStorage("locked") var biometricLock: Bool = false 15 | @AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true 16 | @State private var unlocked = false 17 | 18 | var body: some View { 19 | GeometryReader { geometry in 20 | let screenWidth = geometry.size.width 21 | let screenHeight = geometry.size.height 22 | 23 | NavigationView { 24 | if self.unlocked || !self.biometricLock { 25 | if sessionStore.session != nil { 26 | LoggedUserView() 27 | .ignoresSafeArea(.keyboard) 28 | .navigationBarHidden(true) 29 | } else { 30 | SignInView() 31 | .environmentObject(sessionStore) 32 | .ignoresSafeArea(.keyboard) 33 | } 34 | } else { 35 | VStack { 36 | Text(String(localized: "ContentView_welcome_label")) 37 | .font(.title) 38 | .padding(.bottom, screenHeight * 0.02) 39 | 40 | HStack(spacing: screenWidth * 0.0001) { 41 | Image(uiImage: UIImage(named: colorScheme == .dark ? "FitVeinIconDark" : "FitVeinIconLight")!) 42 | .resizable() 43 | .scaledToFill() 44 | .frame(width: screenWidth * 0.2, height: screenHeight * 0.2) 45 | .padding(.horizontal, screenWidth * 0.05) 46 | 47 | Text("Fit") 48 | .fontWeight(.bold) 49 | .foregroundColor(.accentColor) 50 | Text("Vein") 51 | .fontWeight(.bold) 52 | } 53 | .font(.system(size: screenHeight * 0.1)) 54 | 55 | Spacer() 56 | 57 | Button(action: { 58 | authenticate() 59 | }, label: { 60 | Image(systemName: "faceid") 61 | }) 62 | .font(.system(size: screenHeight * 0.08)) 63 | .padding(.bottom, screenHeight * 0.02) 64 | 65 | Text(String(localized: "ContentView_unlock_the_app_label")) 66 | .foregroundColor(Color(uiColor: UIColor.lightGray)) 67 | 68 | Spacer() 69 | } 70 | .padding() 71 | 72 | } 73 | } 74 | .navigationViewStyle(.stack) 75 | .onAppear { 76 | // In case the app remains in logged-in state 77 | // sessionStore.signOut() 78 | if self.biometricLock && !shouldShowOnboarding { 79 | authenticate() 80 | } 81 | sessionStore.listen() 82 | } 83 | } 84 | } 85 | 86 | func authenticate() { 87 | let context = LAContext() 88 | var error: NSError? 89 | 90 | // check whether biometric authentication is possible 91 | if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { 92 | // it's possible, so go ahead and use it 93 | let reason = "Used to take new pictures of the user." 94 | 95 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in 96 | // authentication has now completed 97 | DispatchQueue.main.async { 98 | if success { 99 | // authenticated successfully 100 | self.unlocked = true 101 | } else { 102 | // there was a problem 103 | } 104 | } 105 | } 106 | } else { 107 | // no biometrics 108 | } 109 | } 110 | } 111 | 112 | struct ContentView_Previews: PreviewProvider { 113 | static var previews: some View { 114 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 115 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 116 | ContentView() 117 | .preferredColorScheme(colorScheme) 118 | .previewDevice(PreviewDevice(rawValue: deviceName)) 119 | .previewDisplayName(deviceName) 120 | .environmentObject(SessionStore(forPreviews: true)) 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Fit Vein/FIrebase Utils/FirebaseStorageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseStorageManager.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 13/10/2021. 6 | // 7 | 8 | import Foundation 9 | import Firebase 10 | 11 | @MainActor 12 | class FirebaseStorageManager: ObservableObject { 13 | private let storageRef = Storage.storage().reference() 14 | 15 | func uploadImageToStorage(image: UIImage, userID: String, completion: @escaping ((String?, Bool) -> ())) { 16 | let imageUUID = UUID().uuidString 17 | let userImagesStorageRef = storageRef.child("images/\(userID)/\(imageUUID)") 18 | 19 | let data = image.jpegData(compressionQuality: 1) 20 | 21 | let metadata = StorageMetadata() 22 | metadata.contentType = "image/jpeg" 23 | metadata.customMetadata = ["username": userID] 24 | 25 | if let data = data { 26 | userImagesStorageRef.putData(data, metadata: metadata) { _, error in 27 | if let error = error { 28 | print("Error uploading photo: \(error.localizedDescription)") 29 | completion(nil, false) 30 | } else { 31 | completion(imageUUID, true) 32 | } 33 | } 34 | } else { 35 | completion(nil, false) 36 | } 37 | } 38 | 39 | func uploadPostImageToStorage(image: UIImage, userID: String, postID: String, completion: @escaping ((String?), Bool) -> ()) { 40 | let imageUUID = UUID().uuidString 41 | let userImagesStorageRef = storageRef.child("images/\(userID)/\(postID)/\(imageUUID)") 42 | 43 | let data = image.jpegData(compressionQuality: 1) 44 | 45 | let metadata = StorageMetadata() 46 | metadata.contentType = "image/jpeg" 47 | metadata.customMetadata = ["username": userID, "post": postID] 48 | 49 | if let data = data { 50 | userImagesStorageRef.putData(data, metadata: metadata) { _, error in 51 | if let error = error { 52 | print("Error uploading photo: \(error.localizedDescription)") 53 | completion(nil, false) 54 | } else { 55 | completion(imageUUID, true) 56 | } 57 | } 58 | } else { 59 | completion(nil, false) 60 | } 61 | } 62 | 63 | func deleteImageFromStorage(userPhotoURL: String, userID: String, completion: @escaping ((Bool) -> ())) { 64 | let userImagesStorageRef = storageRef.child("images/\(userID)/\(userPhotoURL)") 65 | 66 | userImagesStorageRef.delete() { (error) in 67 | if let error = error { 68 | print("Error deleting image from storage: \(error.localizedDescription)") 69 | completion(false) 70 | } else { 71 | print("Successfully deleted image from storage") 72 | completion(true) 73 | } 74 | } 75 | } 76 | 77 | func deletePostImageFromStorage(photoURL: String, userID: String, postID: String, completion: @escaping ((Bool) -> ())) { 78 | let userImagesStorageRef = storageRef.child("images/\(userID)/\(postID)/\(photoURL)") 79 | 80 | userImagesStorageRef.delete() { (error) in 81 | if let error = error { 82 | print("Error deleting image from storage: \(error.localizedDescription)") 83 | completion(false) 84 | } else { 85 | print("Successfully deleted image from storage") 86 | completion(true) 87 | } 88 | } 89 | } 90 | 91 | func getDownloadURLForImage(stringURL: String, userID: String, completion: @escaping ((URL?, Bool) -> ())) { 92 | let path = "images/\(userID)/\(stringURL)" 93 | let userImagesStorageRef = storageRef.child(path) 94 | userImagesStorageRef.downloadURL() { url, error in 95 | if let error = error { 96 | print("Error getting download URL: \(error.localizedDescription)") 97 | completion(nil, false) 98 | } else { 99 | completion(url, true) 100 | } 101 | } 102 | } 103 | 104 | func getDownloadURLForPostImage(stringURL: String, userID: String, postID: String, completion: @escaping ((URL?, Bool) -> ())) { 105 | let path = "images/\(userID)/\(postID)/\(stringURL)" 106 | let userImagesStorageRef = storageRef.child(path) 107 | userImagesStorageRef.downloadURL() { url, error in 108 | if let error = error { 109 | print("Error getting download URL: \(error.localizedDescription)") 110 | completion(nil, false) 111 | } else { 112 | completion(url, true) 113 | } 114 | } 115 | } 116 | 117 | func downloadImageFromStorage(userID: String, userPhotoURL: String, completion: @escaping ((UIImage?, Bool) -> ())) { 118 | let userImagesStorageRef = storageRef.child("images/\(userID)/\(userPhotoURL)") 119 | 120 | userImagesStorageRef.getData(maxSize: 1 * 100 * 1024 * 1024) { (data, error) in 121 | if let error = error { 122 | print("Error downloading file: ", error.localizedDescription) 123 | completion(nil, false) 124 | } else { 125 | if let data = data { 126 | let image = UIImage(data: data)! 127 | completion(image, true) 128 | } else { 129 | completion(nil, false) 130 | } 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Fit Vein/FIrebase Utils/RCValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RCValues.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 03/01/2022. 6 | // 7 | 8 | //import Foundation 9 | //import SwiftUI 10 | //import Firebase 11 | // 12 | //var appPrimaryColor: Color { 13 | // RCValues.sharedInstance.color(forKey: .appPrimaryColor) 14 | //} 15 | // 16 | //class RCValues { 17 | // enum ValueKey: String { 18 | // case appPrimaryColor 19 | // } 20 | // 21 | // static let sharedInstance = RCValues() 22 | // 23 | // var loadingDoneCallback: (() -> Void)? 24 | // var fetchComplete = false 25 | // 26 | // private init() { 27 | // loadDefaultValues() 28 | // fetchCloudValues() 29 | // } 30 | // 31 | // func loadDefaultValues() { 32 | // let appDefaults: [String: Any?] = [ 33 | // ValueKey.appPrimaryColor.rawValue : "#00D100" 34 | // ] 35 | // RemoteConfig.remoteConfig().setDefaults(appDefaults as? [String: NSObject]) 36 | // } 37 | // 38 | // func activateDebugMode() { 39 | // let settings = RemoteConfigSettings() 40 | // // WARNING: Don't actually do this in production! 41 | // settings.minimumFetchInterval = 0 42 | // RemoteConfig.remoteConfig().configSettings = settings 43 | // } 44 | // 45 | // func fetchCloudValues() { 46 | // activateDebugMode() 47 | // 48 | // RemoteConfig.remoteConfig().fetch { [weak self] _, error in 49 | // if let error = error { 50 | // print("Error fetching remote values: \(error)") 51 | // return 52 | // 53 | // } 54 | // 55 | // RemoteConfig.remoteConfig().activate { _, _ in 56 | // print("Retrieved values from the cloud!") 57 | // } 58 | // 59 | // self?.fetchComplete = true 60 | // 61 | // DispatchQueue.main.async { 62 | // self?.loadingDoneCallback?() 63 | // } 64 | // } 65 | // } 66 | // 67 | // func color(forKey key: ValueKey) -> Color { 68 | // let colorAsHexString = RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? "#00D100" 69 | // let convertedColor = Color(hex: colorAsHexString) 70 | // if self.fetchComplete { 71 | // return convertedColor 72 | // } else { 73 | // return Color(.red) 74 | // } 75 | // 76 | // } 77 | // 78 | // func bool(forKey key: ValueKey) -> Bool { 79 | // RemoteConfig.remoteConfig()[key.rawValue].boolValue 80 | // } 81 | // 82 | // func string(forKey key: ValueKey) -> String { 83 | // RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? "" 84 | // } 85 | // 86 | // func double(forKey key: ValueKey) -> Double { 87 | // RemoteConfig.remoteConfig()[key.rawValue].numberValue.doubleValue 88 | // } 89 | //} 90 | -------------------------------------------------------------------------------- /Fit Vein/FIrebase Utils/SessionStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionStore.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 12/10/2021. 6 | // 7 | 8 | import Foundation 9 | import Firebase 10 | import Combine 11 | 12 | struct User { 13 | var uid: String 14 | var email: String 15 | } 16 | 17 | @MainActor 18 | class SessionStore: ObservableObject { 19 | 20 | @Published var session: User? 21 | private var firestoreManager = FirestoreManager() 22 | private var firebaseStorageManager = FirebaseStorageManager() 23 | 24 | var handle: AuthStateDidChangeListenerHandle? 25 | private var authRef = Auth.auth() 26 | public var currentUser = Auth.auth().currentUser 27 | 28 | init(forPreviews: Bool) { 29 | if forPreviews { 30 | self.session = User(uid: "uid", email: "email") 31 | } 32 | } 33 | 34 | func listen() { 35 | handle = authRef.addStateDidChangeListener({ (auth, user) in 36 | if let user = user { 37 | self.session = User(uid: user.uid, email: user.email!) 38 | self.authRef = Auth.auth() 39 | self.currentUser = Auth.auth().currentUser 40 | } else { 41 | self.session = nil 42 | } 43 | }) 44 | } 45 | 46 | func signIn(email: String, password: String, completion: @escaping ((Bool) -> ())) { 47 | authRef.signIn(withEmail: email, password: password) { (result, error) in 48 | if let error = error { 49 | print("Error signing in: \(error.localizedDescription)") 50 | completion(false) 51 | } else { 52 | print("Successfully signed in") 53 | completion(true) 54 | } 55 | } 56 | } 57 | 58 | func signUp(email: String, password: String, completion: @escaping ((String?, Bool) -> ())) { 59 | authRef.createUser(withEmail: email, password: password) { (result, error) in 60 | if let error = error { 61 | print("Error signing up: \(error.localizedDescription)") 62 | completion(nil, false) 63 | } else { 64 | print("Successfully signed up") 65 | completion(result!.user.uid, true) 66 | } 67 | } 68 | } 69 | 70 | func signOut() -> Bool { 71 | do { 72 | try Auth.auth().signOut() 73 | self.session = nil 74 | unbind() 75 | return true 76 | } catch { 77 | } 78 | return false 79 | } 80 | 81 | func unbind () { 82 | if let handle = handle { 83 | Auth.auth().removeStateDidChangeListener(handle) 84 | } 85 | } 86 | 87 | // deinit { 88 | // Task { 89 | // await unbind() 90 | // } 91 | // } 92 | 93 | func sendRecoveryEmail(email: String, completion: @escaping ((Bool) -> ())) { 94 | authRef.sendPasswordReset(withEmail: email) { (error) in 95 | if let error = error { 96 | print("Error sending recovery email: \(error.localizedDescription)") 97 | completion(false) 98 | } else { 99 | print("Recovery email has been sent") 100 | completion(true) 101 | } 102 | } 103 | } 104 | 105 | func changeEmailAddress(userID: String, oldEmailAddress: String, password: String, newEmailAddress: String, completion: @escaping ((Bool) -> ())) { 106 | let credential = EmailAuthProvider.credential(withEmail: oldEmailAddress, password: password) 107 | 108 | currentUser?.reauthenticate(with: credential) { [self] (result, error) in 109 | if let error = error { 110 | print("Error re-authenticating user \(error)") 111 | completion(false) 112 | } else { 113 | currentUser?.updateEmail(to: newEmailAddress) { (error) in 114 | if let error = error { 115 | print("Error changing email address: \(error.localizedDescription)") 116 | completion(false) 117 | } else { 118 | completion(true) 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | func changePassword(emailAddress: String, oldPassword: String, newPassword: String, completion: @escaping ((Bool) -> ())) { 126 | let credential = EmailAuthProvider.credential(withEmail: emailAddress, password: oldPassword) 127 | 128 | currentUser?.reauthenticate(with: credential) { [self] (result, error) in 129 | if let error = error { 130 | print("Error re-authenticating user \(error)") 131 | completion(false) 132 | } else { 133 | currentUser?.updatePassword(to: newPassword) { (error) in 134 | if let error = error { 135 | print("Error changing password: \(error.localizedDescription)") 136 | completion(false) 137 | } else { 138 | print("Successfully changed password") 139 | completion(true) 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | func deleteUser(email: String, password: String, completion: @escaping ((Bool) -> ())) { 147 | let credential = EmailAuthProvider.credential(withEmail: email, password: password) 148 | 149 | currentUser?.reauthenticate(with: credential) { [self] (result, error) in 150 | if let error = error { 151 | print("Error re-authenticating user \(error)") 152 | completion(false) 153 | } else { 154 | currentUser?.delete { (error) in 155 | if let error = error { 156 | print("Could not delete user: \(error)") 157 | completion(false) 158 | } else { 159 | let result = self.signOut() 160 | completion(result) 161 | } 162 | } 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Fit Vein/Fit Vein.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.healthkit 6 | 7 | com.apple.developer.healthkit.access 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Fit Vein/Fit_VeinApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fit_VeinApp.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 12/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | import Firebase 10 | 11 | @main 12 | struct Fit_VeinApp: App { 13 | 14 | @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 15 | 16 | var body: some Scene { 17 | WindowGroup { 18 | let sessionStore = SessionStore(forPreviews: false) 19 | PreLaunchView() 20 | .environmentObject(sessionStore) 21 | } 22 | } 23 | } 24 | 25 | class AppDelegate: NSObject, UIApplicationDelegate { 26 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 27 | FirebaseApp.configure() 28 | 29 | // Only to use along with Firebase A/B Testing (device ID Token / Installation Token) 30 | // Installations.installations().authTokenForcingRefresh(true, completion: { (result, error) in 31 | // if let error = error { 32 | // print("Error fetching token: \(error)") 33 | // return 34 | // } 35 | // guard let result = result else { return } 36 | // print("Installation auth token: \(result.authToken)") 37 | // }) 38 | 39 | // Only to use along with Remote Config values in RCValues class 40 | // _ = RCValues.sharedInstance 41 | 42 | return true 43 | } 44 | } 45 | 46 | extension UIApplication { 47 | struct Constants { 48 | static let CFBundleShortVersionString = "CFBundleShortVersionString" 49 | } 50 | class func appVersion() -> String { 51 | return Bundle.main.object(forInfoDictionaryKey: Constants.CFBundleShortVersionString) as! String 52 | } 53 | 54 | class func appBuild() -> String { 55 | return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String 56 | } 57 | 58 | class func versionBuild() -> String { 59 | let version = appVersion(), build = appBuild() 60 | 61 | return version == build ? "\(version)" : "\(version) (\(build))" 62 | } 63 | } 64 | 65 | extension View { 66 | /// Hide or show the view based on a boolean value. 67 | /// 68 | /// Example for visibility: 69 | /// 70 | /// Text("Label") 71 | /// .isHidden(true) 72 | /// 73 | /// Example for complete removal: 74 | /// 75 | /// Text("Label") 76 | /// .isHidden(true, remove: true) 77 | /// 78 | /// - Parameters: 79 | /// - hidden: Set to `false` to show the view. Set to `true` to hide the view. 80 | /// - remove: Boolean value indicating whether or not to remove the view. 81 | @ViewBuilder func isHidden(_ hidden: Bool, remove: Bool = false) -> some View { 82 | if hidden { 83 | if !remove { 84 | self.hidden() 85 | } 86 | } else { 87 | self 88 | } 89 | } 90 | } 91 | 92 | extension Array: RawRepresentable where Element: Codable { 93 | public init?(rawValue: String) { 94 | guard let data = rawValue.data(using: .utf8), 95 | let result = try? JSONDecoder().decode([Element].self, from: data) 96 | else { 97 | return nil 98 | } 99 | self = result 100 | } 101 | 102 | public var rawValue: String { 103 | guard let data = try? JSONEncoder().encode(self), 104 | let result = String(data: data, encoding: .utf8) 105 | else { 106 | return "[]" 107 | } 108 | return result 109 | } 110 | } 111 | 112 | extension StringProtocol { 113 | var letters: String { 114 | return String(compactMap { 115 | guard let unicodeScalar = $0.unicodeScalars.first else { return nil } 116 | return CharacterSet.letters.contains(unicodeScalar) ? $0 : nil 117 | }) 118 | } 119 | } 120 | 121 | extension StringProtocol { 122 | public func removeCharactersFromString(string: String, character: String, before: Bool, upToCharacter: String?) -> String { 123 | if let startingCharacterindex = string.range(of: character)?.lowerBound { 124 | var substring = "" 125 | 126 | if before { 127 | substring = String(string.prefix(upTo: startingCharacterindex)) 128 | } else { 129 | if upToCharacter != nil { 130 | if let endingCharacterIndex = string.range(of: upToCharacter!)?.lowerBound { 131 | let lastCharacterIndex = string.endIndex 132 | substring = String(string[.. some View { 147 | for v in vars { print(v) } 148 | return EmptyView() 149 | } 150 | } 151 | 152 | extension View { 153 | @ViewBuilder 154 | func `if`(_ condition: Bool, transform: (Self) -> Transform) -> some View { 155 | if condition { transform(self) } 156 | else { self } 157 | } 158 | } 159 | 160 | struct NavigationBarModifier: ViewModifier { 161 | 162 | var backgroundColor: UIColor? 163 | var titleColor: UIColor? 164 | 165 | init(backgroundColor: UIColor?, titleColor: UIColor?) { 166 | self.backgroundColor = backgroundColor 167 | let coloredAppearance = UINavigationBarAppearance() 168 | coloredAppearance.configureWithTransparentBackground() 169 | coloredAppearance.backgroundColor = backgroundColor 170 | coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white] 171 | coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white] 172 | 173 | UINavigationBar.appearance().standardAppearance = coloredAppearance 174 | UINavigationBar.appearance().compactAppearance = coloredAppearance 175 | UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance 176 | } 177 | 178 | func body(content: Content) -> some View { 179 | ZStack{ 180 | content 181 | VStack { 182 | GeometryReader { geometry in 183 | Color(self.backgroundColor ?? .clear) 184 | .frame(height: geometry.safeAreaInsets.top) 185 | .edgesIgnoringSafeArea(.top) 186 | Spacer() 187 | } 188 | } 189 | } 190 | } 191 | } 192 | 193 | extension View { 194 | func navigationBarColor(backgroundColor: UIColor?, titleColor: UIColor?) -> some View { 195 | self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor)) 196 | } 197 | } 198 | 199 | extension UIColor { 200 | convenience init(red: Int, green: Int, blue: Int, a: CGFloat = 1.0) { 201 | self.init( 202 | red: CGFloat(red) / 255.0, 203 | green: CGFloat(green) / 255.0, 204 | blue: CGFloat(blue) / 255.0, 205 | alpha: a 206 | ) 207 | } 208 | 209 | convenience init(rgb: Int, a: CGFloat = 1.0) { 210 | self.init( 211 | red: (rgb >> 16) & 0xFF, 212 | green: (rgb >> 8) & 0xFF, 213 | blue: rgb & 0xFF, 214 | a: a 215 | ) 216 | } 217 | } 218 | 219 | extension UIScreen{ 220 | static let screenWidth = UIScreen.main.bounds.size.width 221 | static let screenHeight = UIScreen.main.bounds.size.height 222 | static let screenSize = UIScreen.main.bounds.size 223 | } 224 | 225 | extension Date: RawRepresentable { 226 | private static let formatter = ISO8601DateFormatter() 227 | 228 | public var rawValue: String { 229 | Date.formatter.string(from: self) 230 | } 231 | 232 | public init?(rawValue: String) { 233 | self = Date.formatter.date(from: rawValue) ?? Date() 234 | } 235 | } 236 | 237 | extension UIApplication { 238 | func endEditing() { 239 | sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Fit Vein/Images/FitVeinIconDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Images/FitVeinIconDark.png -------------------------------------------------------------------------------- /Fit Vein/Images/FitVeinIconLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Images/FitVeinIconLight.png -------------------------------------------------------------------------------- /Fit Vein/Images/blank-profile-hi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Images/blank-profile-hi.png -------------------------------------------------------------------------------- /Fit Vein/Images/gym.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Images/gym.jpg -------------------------------------------------------------------------------- /Fit Vein/Images/sprint2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Images/sprint2.png -------------------------------------------------------------------------------- /Fit Vein/LoggedUserView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedUserView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 20/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LoggedUserView: View { 11 | @StateObject private var homeViewModel = HomeViewModel() 12 | @StateObject private var workoutViewModel = WorkoutViewModel(forPreviews: false) 13 | @StateObject private var profileViewModel = ProfileViewModel() 14 | @StateObject private var medalsViewModel = MedalsViewModel() 15 | @StateObject private var networkManager = NetworkManager() 16 | 17 | @AppStorage("isTabBarHidden") var isTabBarHidden: Bool = false 18 | 19 | @AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true 20 | @State private var shouldShowOnboardingState: Bool = false 21 | 22 | @State var selectedTab: Tab = .home 23 | 24 | enum Tab: String { 25 | case home 26 | case workout 27 | case profile 28 | } 29 | 30 | private var tabItems = [ 31 | TabItem(text: String(localized: "LoggedUserView_home_tab_label"), icon: "house", tab: .home), 32 | TabItem(text: String(localized: "LoggedUserView_workout_tab_label"), icon: "figure.walk", tab: .workout), 33 | TabItem(text: String(localized: "LoggedUserView_profile_tab_label"), icon: "person", tab: .profile) 34 | ] 35 | 36 | var body: some View { 37 | GeometryReader { geometry in 38 | let screenWidth = geometry.size.width 39 | let screenHeight = geometry.size.height 40 | 41 | Group { 42 | switch selectedTab { 43 | case .home: 44 | withAnimation(.linear) { 45 | HomeView() 46 | .environmentObject(homeViewModel) 47 | .environmentObject(profileViewModel) 48 | .environmentObject(medalsViewModel) 49 | .environmentObject(networkManager) 50 | .onAppear { 51 | UserDefaults.standard.set(false, forKey: "isTabBarHidden") 52 | } 53 | } 54 | case .workout: 55 | withAnimation(.linear) { 56 | WorkoutView() 57 | .environmentObject(workoutViewModel) 58 | .environmentObject(medalsViewModel) 59 | .environmentObject(networkManager) 60 | .onAppear { 61 | UserDefaults.standard.set(false, forKey: "isTabBarHidden") 62 | } 63 | } 64 | case .profile: 65 | withAnimation(.linear) { 66 | ProfileView() 67 | .environmentObject(profileViewModel) 68 | .environmentObject(medalsViewModel) 69 | .environmentObject(networkManager) 70 | .onAppear { 71 | UserDefaults.standard.set(false, forKey: "isTabBarHidden") 72 | } 73 | } 74 | } 75 | } 76 | .frame(maxWidth: .infinity, maxHeight: .infinity) 77 | 78 | HStack { 79 | Spacer() 80 | 81 | ForEach(tabItems) { tabItem in 82 | Spacer() 83 | 84 | Button { 85 | withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { 86 | selectedTab = tabItem.tab 87 | } 88 | } label: { 89 | VStack(spacing: 0) { 90 | Image(systemName: tabItem.icon) 91 | .symbolVariant(.fill) 92 | .font(.body.bold()) 93 | .frame(width: 44, height: 29) 94 | Text(tabItem.text) 95 | .font(.caption2) 96 | .lineLimit(1) 97 | } 98 | .frame(maxWidth: .infinity) 99 | .foregroundColor(selectedTab == tabItem.tab ? .accentColor : Color(uiColor: .systemGray)) 100 | } 101 | .foregroundStyle(selectedTab == tabItem.tab ? .primary : .secondary) 102 | 103 | Spacer() 104 | } 105 | 106 | Spacer() 107 | } 108 | .padding(.horizontal, 7) 109 | .padding(.top, 10) 110 | .frame(height: screenHeight * 0.11, alignment: .top) 111 | .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 40, style: .continuous)) 112 | .overlay( 113 | HStack { 114 | if selectedTab == .workout { 115 | Spacer() 116 | } 117 | 118 | if selectedTab == .profile { 119 | Spacer() 120 | } 121 | 122 | Rectangle() 123 | .fill(Color.accentColor) 124 | .frame(width: screenWidth * 0.15, height: screenHeight * 0.007) 125 | .cornerRadius(3) 126 | .frame(width: screenWidth * 0.235) 127 | .frame(maxHeight: .infinity, alignment: .top) 128 | 129 | if selectedTab == .home { 130 | Spacer() 131 | } 132 | 133 | if selectedTab == .workout { 134 | Spacer() 135 | } 136 | } 137 | .padding(selectedTab == .home ? .leading : .trailing, selectedTab == .workout ? 0 : screenWidth * 0.074) 138 | ) 139 | .frame(maxHeight: .infinity, alignment: .bottom) 140 | .ignoresSafeArea() 141 | .isHidden(isTabBarHidden) 142 | } 143 | .onAppear { 144 | self.shouldShowOnboardingState = shouldShowOnboarding 145 | } 146 | .fullScreenCover(isPresented: $shouldShowOnboarding, onDismiss: { 147 | shouldShowOnboarding = false 148 | shouldShowOnboardingState = false 149 | }, content: { 150 | OnboardingView() 151 | }) 152 | } 153 | 154 | struct TabItem: Identifiable { 155 | var id = UUID() 156 | var text: String 157 | var icon: String 158 | var tab: Tab 159 | } 160 | } 161 | 162 | struct LoggedUserView_Previews: PreviewProvider { 163 | static var previews: some View { 164 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 165 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 166 | LoggedUserView() 167 | .preferredColorScheme(colorScheme) 168 | .previewDevice(PreviewDevice(rawValue: deviceName)) 169 | .previewDisplayName(deviceName) 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Fit Vein/Lottie/70772-smiley-loader.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.21","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":361,"w":400,"h":400,"nm":"Loader","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Left Eye","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.555,"y":1},"o":{"x":1,"y":0},"t":60,"s":[800,627.5,0],"to":[-4.5,2.083,0],"ti":[4.5,-2.083,0]},{"i":{"x":0.335,"y":0.335},"o":{"x":0.167,"y":0.167},"t":120,"s":[773,640,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.763,"y":1},"o":{"x":1,"y":0},"t":257,"s":[773,640,0],"to":[4.5,-2.083,0],"ti":[-4.5,2.083,0]},{"t":310,"s":[800,627.5,0]}],"ix":2},"a":{"a":0,"k":[20,-36,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[0,0,100]},{"i":{"x":[0.555,0.555,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":60,"s":[140,140,100]},{"i":{"x":[0.497,0.497,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":120,"s":[100,100,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.167],"y":[0,0,0]},"t":257,"s":[100,100,100]},{"i":{"x":[0.117,0.117,0.833],"y":[1,1,1]},"o":{"x":[0.14,0.14,0.333],"y":[0,0,0]},"t":310,"s":[140,140,100]},{"t":359,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[35,35],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":124,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.094117647059,0.250980392157,0.160784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[20,-36],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Right Eye","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.555,"y":1},"o":{"x":1,"y":0},"t":60,"s":[800,627.5,0],"to":[4.667,2.083,0],"ti":[-4.667,-2.083,0]},{"i":{"x":0.76,"y":0.76},"o":{"x":0.167,"y":0.167},"t":120,"s":[828,640,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.769,"y":1},"o":{"x":1,"y":0},"t":257,"s":[828,640,0],"to":[-4.667,-2.083,0],"ti":[4.667,2.083,0]},{"t":310,"s":[800,627.5,0]}],"ix":2},"a":{"a":0,"k":[20,-36,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[0,0,100]},{"i":{"x":[0.555,0.555,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":60,"s":[140,140,100]},{"i":{"x":[0.497,0.497,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":120,"s":[100,100,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.167],"y":[0,0,0]},"t":257,"s":[100,100,100]},{"i":{"x":[0.112,0.112,0.833],"y":[1,1,1]},"o":{"x":[0.141,0.141,0.333],"y":[0,0,0]},"t":310,"s":[140,140,100]},{"t":359,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0.457],"y":[1,1]},"o":{"x":[0.936,0.455],"y":[0,0]},"t":150,"s":[35,35]},{"i":{"x":[0.748,0.667],"y":[1,1]},"o":{"x":[0.862,0.333],"y":[0,0]},"t":170,"s":[35,12]},{"i":{"x":[0,0.457],"y":[1,1]},"o":{"x":[0.392,0.855],"y":[0,0]},"t":182,"s":[38,10]},{"i":{"x":[0.107,0.667],"y":[1,1]},"o":{"x":[0.862,0.333],"y":[0,0]},"t":193,"s":[35,12]},{"t":213,"s":[35,35]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":124,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.094117647059,0.250980392157,0.160784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[20,-36],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Smile Lip","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.239],"y":[1]},"o":{"x":[0.373],"y":[0]},"t":60,"s":[-180]},{"i":{"x":[0.452],"y":[1]},"o":{"x":[0.877],"y":[0]},"t":77,"s":[-210]},{"i":{"x":[0.381],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":120,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.015],"y":[0]},"t":230,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.836],"y":[0]},"t":247,"s":[-20]},{"t":310,"s":[180]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.761,"y":0.761},"o":{"x":0.167,"y":0.167},"t":120,"s":[800,640,0],"to":[0,0,0],"ti":[0,0,0]},{"t":230,"s":[800,640,0]}],"ix":2},"a":{"a":0,"k":[-136.248,-44.248,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.761,0.761,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":120,"s":[100.678,100,100]},{"t":230,"s":[100.678,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[147.503,147.503],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":124,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.094117647059,0.250980392157,0.160784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":16,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-136.248,-44.248],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[11]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.134],"y":[0]},"t":230,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":310,"s":[0]},{"t":359,"s":[11]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[11]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[23]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.134],"y":[0]},"t":230,"s":[23]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":310,"s":[23]},{"t":359,"s":[11]}],"ix":2},"o":{"a":0,"k":48,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Smile_lottie","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,144,0],"ix":2},"a":{"a":0,"k":[800,600,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":1600,"h":1200,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"BG","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[202.25,204,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[800,600],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.373502604167,1,0.643871292413,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/downArrows.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":30,"ip":0,"op":33,"w":500,"h":500,"nm":"arrow animation","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 5 Outlines","sr":1,"ks":{"o":{"a":0,"k":50,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":2,"s":[250,178,0],"to":[0,6.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[250,218.00000000000003,0],"to":[0,0,0],"ti":[0,6.667,0]},{"t":30,"s":[250,178,0]}],"ix":2},"a":{"a":0,"k":[72.596,41.955,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[57.596,-26.956],[-0.001,26.956],[-57.597,-26.956]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.25098039215686274,0.8392156862745098,0.20392156862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[72.596,41.955],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":35,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 4 Outlines","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":3,"s":[250,250.99999999999997,0],"to":[0,6.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":17,"s":[250,291,0],"to":[0,0,0],"ti":[0,6.667,0]},{"t":32,"s":[250,250.99999999999997,0]}],"ix":2},"a":{"a":0,"k":[72.596,41.955,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[57.596,-26.955],[-0.001,26.955],[-57.597,-26.955]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.25098039215686274,0.8392156862745098,0.20392156862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[72.596,41.955],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":35,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 3 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[250,324,0],"to":[0,6.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[250,364,0],"to":[0,0,0],"ti":[0,6.667,0]},{"t":34,"s":[250,324,0]}],"ix":2},"a":{"a":0,"k":[132.596,101.955,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[57.596,-26.955],[-0.001,26.955],[-57.597,-26.955]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.25098039215686274,0.8392156862745098,0.20392156862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[132.596,101.955],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":35,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/heartRate.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.8","fr":29.9700012207031,"ip":0,"op":219.000008920053,"w":500,"h":500,"nm":"Comp 1","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1040,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-5.667,-29.333],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-1032,24],[-952,24],[-929,-63],[-912,25],[-881,25],[-856,-130],[-822,119],[-811,18],[-762,18],[-746,-78],[-722,16],[-637,16],[-614,114],[-598,18],[-498,18]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8156862745098039,0.00784313725490196,0.10588235294117647,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":52,"s":[0]},{"t":174.000007087165,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"t":127.000005172816,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":180.00000733155,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Beat1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[992,250,0],"ix":2},"a":{"a":0,"k":[1000,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":500,"ip":213.000008675668,"op":386.000015722102,"st":206.000008390552,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Beat1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[992,250,0],"ix":2},"a":{"a":0,"k":[1000,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":500,"ip":98.0000039916218,"op":278.000011323172,"st":98.0000039916218,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Beat1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[992,250,0],"ix":2},"a":{"a":0,"k":[1000,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":500,"ip":-2.00000008146167,"op":166.000006761319,"st":-14.0000005702317,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1033,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-5.667,-29.333],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-1035,24],[-952,24],[-929,-63],[-912,25],[-881,25],[-856,-130],[-822,119],[-811,18],[-762,18],[-746,-78],[-722,16],[-637,16],[-614,114],[-598,18],[-498,18]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8156862745098039,0.00784313725490196,0.10588235294117647,1],"ix":3},"o":{"a":0,"k":30,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":385.000015681371,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/loading.json: -------------------------------------------------------------------------------- 1 | {"v":"5.8.1","fr":29.9700012207031,"ip":0,"op":50.0000020365418,"w":568,"h":228,"nm":"Loading","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":16,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[100]},{"t":40.0000016292334,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[193,126.453,0],"ix":2,"l":2},"a":{"a":0,"k":[-144.773,14,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,-0.174]},"t":0,"s":[67.692,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":21,"s":[94.692,139.886,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,6.95]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":25,"s":[94,138.864,100]},{"t":47.0000019143492,"s":[67,98.977,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[36,115],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.274563389198,0.637646963082,0.308735985849,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-148,12.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50.0000020365418,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":23,"s":[100]},{"t":44.0000017921567,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[275.273,126.458,0],"ix":2,"l":2},"a":{"a":0,"k":[-144.773,14,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[67.692,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,-0.25]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":25,"s":[94.692,139.886,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":29,"s":[94,138.864,100]},{"t":51.0000020772726,"s":[67,98.977,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[36,115],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.274563389198,0.637646963082,0.308735985849,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-148,12.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":4.00000016292334,"op":54.0000021994651,"st":4.00000016292334,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":26,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[100]},{"t":50.0000020365418,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[357,126.088,0],"ix":2,"l":2},"a":{"a":0,"k":[-144.773,14,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,-0.174]},"t":10,"s":[67.692,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":31,"s":[94.692,139.886,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,6.95]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":35,"s":[94,138.864,100]},{"t":57.0000023216576,"s":[67,98.977,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[36,115],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.274563389198,0.637646963082,0.308735985849,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-148,12.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":10.0000004073083,"op":60.0000024438501,"st":10.0000004073083,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/skeleton.json: -------------------------------------------------------------------------------- 1 | {"v":"4.8.0","meta":{"g":"LottieFiles AE ","a":"","k":"","d":"","tc":""},"fr":24,"ip":0,"op":48,"w":650,"h":611,"nm":"Artboard 1","ddd":0,"assets":[{"id":"image_0","w":650,"h":611,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAooAAAJjCAYAAACLPzDGAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAASH0lEQVR4nO3dT4jnd33H8dd7d8gfyGGKF2m7YS1ii4fteqk5Bg+mgRiWKvRQqIUGilZk8ZSDh5BzDk21oYe0lEAlUANBF+lByh6aQKWUZU+FGlxcWfCUkRq6s2b89LBjq/DZmfjb72c/8/vO43H08P6+Mof45Pub+SUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCkq5HHW2uXklwc+Yx72EvyZlXdmPBsAADupbW221r7QWvtZ22uL87+WQAAbKshbxRbaz9Icn7E7V/TQZLPVdWbs4cAAGybM0sfbHc/bv7tpe9u6GySv549AgBgGy0eirn7O4k7A+5u6tzsAQAA22hEKAIAsAJCEQCALqEIAECXUAQAoOsk/dHJ1mmt7SZ5NckzSR6ePIf1+F6Sv6iqa7OHAHC6eaO4ocNIvJ7ksxGJLOsPkvxba+3J2UMAON2E4uZeja/eYZyHkviieACmEoqbe2b2AFbvUW8VAZhJKG7Ox82M9tDsAQCcbkIRAIAuoQgAQJdQBACgSygCANAlFAEA6BKKAAB0CUUAALqEIgAAXUIRAIAuoQgAQJdQ3Nz+7AGs3p3ZAwA43YTi5q7MHsDq/U9VXZ09AoDTSyhu7rkkN2ePYLXuJLk0ewQAp5tQ3FBV7SW5kOSN+BiaZX0vySe9TQRgtp3ZA7bZYSx+bvYOAIARvFEEAKBLKAIA0CUUAQDoEooAAHSNCMW9ATfvh79IBgDYwIhQfHPAzfvhi7EBADaweChW1Y0kf5nkYOnbG7iZu1+MDQDASdFau9Ra+2Gb43Zr7Zuttd3ZPwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAXUiKOttd0kryZ5JsnDI54BALCF9pNcSfJcVe3NHnOcxUPxMBKvJzm39G0AgJW4meTCSY/FMwNuvhqRCABwlHO520wn2og3irfj42YAgOPsV9Ujs0ccZUQotqVvAgCsUVUN+XuRpYz46BkAgBUQigAAdAlFAAC6hCIAAF1CEQCALqEIAECXUAQAoEsoAgDQJRQBAOgSigAAdI0Ixf0BNwEA1ubEN9OIULwy4CYAwNqc+GZa/D9E3VrbTXI9ybmlbwMArMTNJBeqam/2kKMs/kbx8B/4QpI3sgWvVAEAHqD93G2kEx+JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAiVOjDrfWdpO8muSZJA+Peg4AwBbZT3IlyXNVtTd7zHGGhOJhJF5Pcm7EfQCALXczyYWTHotnBt19NSIRAOBezuVuL51oo94o3o6PmwEAjrJfVY/MHnGUUaHYRtwFAFiTqhr29yJLGPXRMwAAW04oAgDQJRQBAOgSigAAdAlFAAC6hCIAAF1CEQCALqEIAECXUAQAoEsoAgDQNSoU9wfdBQBYixPfS6NC8cqguwAAa3Hie2nIf4i6tbab5HqScyPuAwBsuZtJLlTV3uwhRxnyRvHwH/pCkjeyBa9VAQAekP3c7aMTH4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsKQacbS1tpvk1STPJHl4xDMASJK8leRLVXVt9hBgfRYPxcNIvJ7k3NK3Aei6k+STYhFY2pkBN1+NSAR4kB5K8i+zRwDrM+KN4u34uBngQfNWEVjciDeKIhHgwXsoye7sEcC6jAhFAABWQCgCANAlFAEA6BKKAAB0CUUAALqEIgAAXUIRAIAuoQgAQJdQBACgSygCANA1IhT3B9wE4Gh3kuzNHgGsy4hQvDLgJgBHe6+qrs0eAazLiFB8LsnNAXcB6LuT5FOzRwDrs3goVtVekgtJ3oiPoQFGeyvJJ71NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOOVq5PHW2p8leXLkM+5hL8k/VNW1Cc8GAOBeWmsXW2s/bK39rM3zs9ba38/+WQAA8EsOI/EkOGitfXH2zwMAYBst/tFza+1Skn9KsrP07Q3dqqrfmj0CAGDbnBlw82JOTiQmyW/OHgAAsI1GhCIAACsgFAEA6BKKAAB0CUUAALpO0h+dbJ3W2m6S15M8NXsLq3IzyZer6s3ZQwA43bxR3NBhJN6KSGR555J8s7X2J7OHAHC6CcXNvZbk0dkjWK2zSf5u9ggATjehuLlPzx7A6lVr7cnZIwA4vYTi5h6ePYDVe2j2AABON6EIAECXUAQAoEsoAgDQJRQBAOgSigAAdAlFAAC6hCIAAF1CEQCALqEIAECXUAQAoEsobu7d2QNYvTtJ9maPAOD0Eoqbey3JwewRrNp7VXVt9ggATi+huKGqupzkVsQiY9xJ8qnZIwA43YTifaiqx5O8lOSd2VtYjZ8k+XaS3/U2EYDZdmYP2HZV9XyS52fvAABYmjeKAAB0CUUAALqEIgAAXUIRAICuEaF4I8n7A+5uan/2AACAbTQiFN/Myfpuwb+dPQAAYBstHopVtZfkz5e+u4H3k9xM8sLkHQAA/LLW2sXW2r+21vbag/f91tpftdZ2Z/8cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWUCOOttZ2k7ye5KkR9wEAttS7Sb5aVa/MHvJBLB6Kh5F4K8mjS98GAFiBnyf5WlVdnj3kOCNC8VtJPrP0XQCAlflIVd2YPeIoZwbc/PSAmwAAa3Np9oDjjAjFhwfcBABYm93ZA44zIhQBAFgBoQgAQJdQBACgSygCANAlFAEA6BKKAAB0CUUAALqEIgAAXUIRAIAuoQgAQNeIUHx3wE0AgLW5MXvAcUaE4mtJDgbcBQBYi/0kb84ecZzFQ7GqLie5FbEIAHAvX6mqvdkjjjPkdxSr6vEkLyV5Z8R9AIAt9JMkbyX5RFW9MnsMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwdWrE0dbabpLXkzw14j4AwJZ6N8lXq+qV2UM+iMVD8TASbyV5dOnbAAAr8PMkX6uqy7OHHGdEKH4ryWeWvgsAsDIfqaobs0cc5cyAm58ecBMAYG0uzR5wnBGh+PCAmwAAa7M7e8BxRoQiAAArIBQBAOgSigAAdAlFAAC6hCIAAF1CEQCALqEIAECXUAQAoEsoAgDQJRQBAOgaEYrvDrgJALA2N2YPOM6IUHwtycGAuwAAa7Gf5M3ZI46zeChW1eUktyIWAQDu5StVtTd7xHGG/I5iVT2e5KUk74y4DwCwhX6S5K0kn6iqV2aPAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAHqUYcba3tJnk9yVMj7gPwf24m+dOqujp7CLA+i4fiYSTeSvLo0rcB6DpI8vmq+sfZQ4B1GRGK30zy2aXvAnCk/ap6ZPYIYF3ODLj5zICbABytWmtPzh4BrMuIUHx4wE0AjvbQ7AHA+owIRQAAVkAoAgDQJRQBAOgSigAAdAlFAAC6hCIAAF1CEQCALqEIAECXUAQAoEsoAgDQNSIU3x1wE4Cj3UmyN3sEsC4jQvG1JAcD7gJwb/9dVddmjwDWpUYcba39MMlvJjk74j4Av2I/yRNCEVjakN9RrKrHk7yU5J0R9wFIkvw4ybeT/J5IBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOOVq1OHW2m6SLyT541HPOMJPk3yjql6Z8GwAgFUYEoqttfNJ/j3Jh0bc/4AOktxM8omq2pu4AwBgK50ZdPdK5kZikpxNcj7J30zeAQCwlRZ/o9hau5i7bxPPLn17U1U17CN2AIC1GvFGcTcnKBKT/LS19uTsEQAA22bUR88nyfuzBwAAbKPTEIoAAGxAKAIA0LUze8AaHH4d0PnJM1iPa77SCYCTQCjeh8MvFb+a5ONJ3pu7hpXYSfJYa+3lqro8ewwAp5tQ3NBhJF5Pcu7wf9qdOIf1+VJr7Xeq6tnZQwA4vfyO4uZeyP9HIiztbJKnD7+XFACmEIqb+8PZA1i9nSSXZo8A4PQSipt7ZPYAAICRhCIAAF1CEQCALqEIAECXUAQAoEsoAgDQJRQBAOgSigAAdAlFAAC6hCIAAF1CEQCALqG4uR/NHsDqHSS5MXsEAKeXUNzcV3P3/8hhlPeTvDl7BACnl1DcUFVdTfJSxCJj7Cd5oqr2Zg8B4PTamT1gm1XV8621f07ylSQfj58n9+92kreTvFhVNyZvAeCUEzb36fDN4tXJMwAAFuejZwAAuoQiAABdQhEAgK7TEIp+DxMAYAMjQnEvyZ0Bdzf12OEfnAAA8GtYPBSr6lqS/1r67n349uwBAADbqEYcba2dT/IfSX5jxP0P6CDJrSQXfGkxAMCvb0goJklrbTd3v4j6j5I8Nuo59/CjJN+oqlce8HMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOA+1cjjrbXzSc6PfAYAwBa5VlV7s0d8UENC8TAQryT5WJL3RjwDAGDL7CR5LMnLVXV59pgPYvFQbK3tJvl+kg8tfRsAYAUOknynqp6dPeQ4ZwbcfCEiEQDgXs4mebq1dnH2kOOMCMVnBtwEAFiTnSSXZo84zohQ3BlwEwCAB2xEKAIAsAJCEQCALqEIAECXUAQAoEsoAgDQJRQBAOgSigAAdAlFAAC6hCIAAF1CEQCArhGh+OMBNwEA1uQgyY3ZI45TSx9srT2Z5LtJzi59GwBgJfaTfLiq9mYPOcribxSr6mqSr+duKQMA8Kv2kzxx0iMxGfBG8RcO3yw+n+SjSXZGPQcAYEvcTvJ2kher6sbkLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJxoNfJ4a+18kvMjnwEAsEWuVdXe7BEf1JBQPAzEK0k+luS9Ec8AANgyO0keS/JyVV2ePeaDWDwUW2u7Sb6f5ENL3wYAWIGDJN+pqmdnDznOmQE3X4hIBAC4l7NJnm6tXZw95DgjQvGZATcBANZkJ8ml2SOOMyIUdwbcBADgARsRigAArIBQBACgSygCANAlFAEA6BKKAAB0CUUAALqEIgAAXUIRAIAuoQgAQJdQBACga0Qo/njATQCANTlIcmP2iOPU0gdba08m+W6Ss0vfBgBYif0kH66qvdlDjrL4G8Wquprk67lbygAA/Kr9JE+c9EhMBrxR/IXDN4vPJ/lokp1RzwEA2BK3k7yd5MWqujF5CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAKDXyeGvtfJLzI58BcMpdq6q92SOAdRoSiq213SRXk/x+Ev8CAxhjJ8ljSV6uqsuzxwDrs3goHkbi9STnlr4NQNdBku9U1bOzhwDrcmbAzS9EJAI8SGeTPN1auzh7CLAuI0Lx8wNuAnC0nSSXZo8A1mVEKD4y4CYAAA/YiFAEAGAFhCIAAF1CEQCALqEIAECXUAQAoEsoAgDQJRQBAOgSigAAdAlFAAC6hCIAAF0jQvE/B9wE4GgHSW7MHgGsSy19sLX2ZJLvJjm79G0A7mk/yYeram/2EGA9Fn+jWFVXk3w5yftL3wagaz/JEyIRWNribxR/4fDN4leSfCzJI6OeA3CK3U7ydpIXq+rG5C0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBJ8b8tucGgzZO/EAAAAABJRU5ErkJggg==","e":1}],"layers":[{"ddd":0,"ind":1,"ty":2,"nm":"Artboard 1.png","cl":"png","refId":"image_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":24,"s":[14]},{"t":48,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[325,305.5,0],"ix":2},"a":{"a":0,"k":[325,305.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":48,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/success2.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":30,"ip":0,"op":60,"w":608,"h":608,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"thik chino","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[301.5,281,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-119,30],[-44,96],[112,-50]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":17,"s":[0]},{"t":37,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":25,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"fill","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[304,304,0],"ix":2},"a":{"a":0,"k":[-38,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16,"s":[0,0,100]},{"t":21,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.516,0],[0,-100.516],[-100.516,0],[0,100.516]],"o":[[-100.516,0],[0,100.516],[100.516,0],[0,-100.516]],"v":[[0,-182],[-182,0],[0,182],[182,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.223528992896,0.349019996793,0.517646998985,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.170349854114,0.819607843137,0.445329823213,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-38,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"stock","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[304,304,0],"ix":2},"a":{"a":0,"k":[-38,0,0],"ix":1},"s":{"a":0,"k":[107,107,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.516,0],[0,-100.516],[-100.516,0],[0,100.516]],"o":[[-100.516,0],[0,100.516],[100.516,0],[0,-100.516]],"v":[[0,-182],[-182,0],[0,182],[182,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.170934250776,0.764705882353,0.424896808699,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":25,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-38,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/time.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.4","fr":60,"ip":0,"op":480,"w":48,"h":48,"nm":"Timer","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"seconds","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[235]},{"t":479,"s":[595]}],"ix":10},"p":{"a":0,"k":[24.375,23.781,0],"ix":2},"a":{"a":0,"k":[-21.625,0.32,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":100,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.49411764705882355,0.8274509803921568,0.12941176470588237,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.49411764705882355,0.8274509803921568,0.12941176470588237,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-21.625,0.5],"ix":2},"a":{"a":0,"k":[0,6.25],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":480,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"minutes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24.375,23.781,0],"ix":2},"a":{"a":0,"k":[-21.625,2.422,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":112,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.49411764705882355,0.8274509803921568,0.12941176470588237,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.49411764705882355,0.8274509803921568,0.12941176470588237,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-21.625,-5.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":480,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"background","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24.379,23.758,0],"ix":2},"a":{"a":0,"k":[0.504,2.008,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.5,24.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.49411764705882355,0.8274509803921568,0.12941176470588237,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.26666666666666666,0.2549019607843137,0.2549019607843137,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.5,2],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[186.718,186.718],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":480,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/time2.json: -------------------------------------------------------------------------------- 1 | {"v":"5.4.3","fr":29.9700012207031,"ip":0,"op":60.0000024438501,"w":640,"h":640,"nm":"Comp 2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[313.25,237.77,0],"ix":2},"a":{"a":0,"k":[2.596,-12.179,0],"ix":1},"s":{"a":0,"k":[1054.029,1054.029,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.829,0],[0,0],[0,-0.828],[-0.829,0],[0,0],[0,0.828]],"o":[[0,0],[-0.829,0],[0,0.828],[0,0],[0.829,0],[0,-0.828]],"v":[[17.887,6.197],[7.212,6.197],[5.712,7.697],[7.212,9.197],[17.887,9.197],[19.387,7.697]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8156862745098039,0.00784313725490196,0.10588235294117647,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.29,"y":1},"o":{"x":0.71,"y":0},"n":"0p29_1_0p71_0","t":0,"s":[-0.231,-25.763],"e":[-0.231,-24.909],"to":[0,0],"ti":[0,0]},{"i":{"x":0.29,"y":1},"o":{"x":0.71,"y":0},"n":"0p29_1_0p71_0","t":9,"s":[-0.231,-24.909],"e":[-0.231,-25.763],"to":[0,0],"ti":[0,0]},{"t":13.0000005295009}],"ix":2},"a":{"a":0,"k":[9.722,7.87],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 4","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.829,0],[0,0],[0,-0.828],[-0.829,0],[0,0],[0,0.828]],"o":[[0,0],[-0.829,0],[0,0.828],[0,0],[0.829,0],[0,-0.828]],"v":[[3.535,-2.136],[-8.804,-2.136],[-10.304,-0.636],[-8.804,0.864],[3.535,0.864],[5.035,-0.636]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8156862745098039,0.00784313725490196,0.10588235294117647,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.773,-1.262],"ix":2},"a":{"a":0,"k":[3.542,-0.631],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":12,"s":[-125],"e":[595]},{"t":55.0000022401959}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 5","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.829,0],[0,0],[0,-0.828],[-0.829,0],[0,0],[0,0.828]],"o":[[0,0],[-0.829,0],[0,0.828],[0,0],[0.829,0],[0,-0.828]],"v":[[3.535,-2.136],[-8.804,-2.136],[-10.304,-0.636],[-8.804,0.864],[3.535,0.864],[5.035,-0.636]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.662,0.347],"ix":2},"a":{"a":0,"k":[5.15,-0.521],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-89,"ix":10},"p":{"a":0,"k":[312.714,348,0],"ix":2},"a":{"a":0,"k":[0.579,0.579,0],"ix":1},"s":{"a":0,"k":[1054.029,1054.029,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[10.204,0],[0,10.204],[-10.204,0],[0,-10.204]],"o":[[-10.204,0],[0,-10.204],[10.204,0],[0,10.204]],"v":[[0.5,19.005],[-18.005,0.5],[0.5,-18.005],[19.005,0.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.858,0],[0,-11.858],[-11.858,0],[0,11.857]],"o":[[-11.858,0],[0,11.857],[11.858,0],[0,-11.858]],"v":[[0.579,-20.927],[-20.927,0.579],[0.579,22.084],[22.084,0.579]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[23.727,23.032],"ix":2},"a":{"a":0,"k":[23.727,23.032],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 5","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/Lottie/wrongData.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":240,"w":1920,"h":1080,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[960,540,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[69.737,69.737,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-150,-151],[152,151]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":62,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.429],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":91,"s":[0]},{"t":121,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,540,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[69.737,69.737,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-150,-151],[152,151]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":62,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.429],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":91,"s":[0]},{"t":121,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,540,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[0,0,100]},{"t":91,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[720,720],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":58,"s":[100]},{"t":121,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.487058877945,0.067615278065,0.067615278065,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[86.756,86.756],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,540,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[0,0,100]},{"t":91,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[720,720],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.869803873698,0.243545158237,0.243545158237,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[86.756,86.756],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":240,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /Fit Vein/LottieView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LottieView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 09/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | import Lottie 10 | 11 | struct LottieView: UIViewRepresentable { 12 | var name: String 13 | var loopMode: LottieLoopMode = .loop 14 | var contentMode: UIView.ContentMode = .scaleAspectFit 15 | var paused: Bool = false 16 | 17 | var animationView = AnimationView() 18 | 19 | func makeUIView(context: UIViewRepresentableContext) -> UIView { 20 | let view = UIView(frame: .zero) 21 | 22 | animationView.animation = Animation.named(name) 23 | animationView.contentMode = contentMode 24 | animationView.loopMode = loopMode 25 | animationView.backgroundBehavior = .pauseAndRestore 26 | animationView.play() 27 | 28 | animationView.translatesAutoresizingMaskIntoConstraints = false 29 | view.addSubview(animationView) 30 | 31 | NSLayoutConstraint.activate([ 32 | animationView.heightAnchor.constraint(equalTo: view.heightAnchor), 33 | animationView.widthAnchor.constraint(equalTo: view.widthAnchor) 34 | ]) 35 | 36 | return view 37 | } 38 | 39 | func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext) { 40 | } 41 | 42 | func changeAnimationState(pause: Bool) { 43 | if pause { 44 | self.animationView.pause() 45 | } else { 46 | self.animationView.play() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Fit Vein/Medals/medal-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medal-2.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medal-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medal-3.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medal-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medal-4.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medal.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalFirstComment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalFirstComment.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalFirstLike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalFirstLike.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalFirstOwnWorkout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalFirstOwnWorkout.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalFirstPost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalFirstPost.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalFirstWorkout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalFirstWorkout.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalSecondLevel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalSecondLevel.png -------------------------------------------------------------------------------- /Fit Vein/Medals/medalThirdLevel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Medals/medalThirdLevel.png -------------------------------------------------------------------------------- /Fit Vein/Models/HealthStat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthStat.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 24/11/2021. 6 | // 7 | 8 | import Foundation 9 | import HealthKit 10 | 11 | struct HealthStat: Identifiable { 12 | let id: UUID 13 | let stat: HKQuantity? 14 | let date: Date 15 | 16 | init(id: UUID = UUID(), stat: HKQuantity?, date: Date) { 17 | self.id = id 18 | self.stat = stat 19 | self.date = date 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fit Vein/Models/IntervalWorkout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntervalWorkout.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 25/10/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct IntervalWorkout: Codable, Identifiable { 11 | var id: String 12 | var usersID: String 13 | var type: String 14 | var date: Date 15 | var isFinished: Bool 16 | var duration: Int? 17 | var calories: Int? 18 | var series: Int? 19 | var workTime: Int? 20 | var restTime: Int? 21 | var completedDuration: Int? 22 | var completedSeries: Int? 23 | 24 | init(id: String, usersID: String, type: String, date: Date, isFinished: Bool = false, calories: Int?, series: Int?, workTime: Int?, restTime: Int?) { 25 | self.id = id 26 | self.usersID = usersID 27 | self.type = type 28 | self.date = date 29 | self.duration = (series! * workTime!) + (series! * restTime!) 30 | self.isFinished = isFinished 31 | self.calories = calories 32 | self.series = series 33 | self.workTime = workTime 34 | self.restTime = restTime 35 | } 36 | 37 | init(forPreviews: Bool, id: String, usersID: String, type: String, date: Date, isFinished: Bool = false, calories: Int?, series: Int?, workTime: Int?, restTime: Int?, completedDuration: Int?, completedSeries: Int?) { 38 | self.id = id 39 | self.usersID = usersID 40 | self.type = type 41 | self.date = date 42 | self.duration = (series! * workTime!) + (series! * restTime!) 43 | self.isFinished = isFinished 44 | self.calories = calories 45 | self.series = series 46 | self.workTime = workTime 47 | self.restTime = restTime 48 | self.completedDuration = completedDuration 49 | self.completedSeries = completedSeries 50 | } 51 | 52 | mutating func setDataOnEnd(calories: Int?, completedDuration: Int?, completedSeries: Int?) { 53 | self.isFinished = true 54 | self.calories = calories 55 | self.completedDuration = completedDuration 56 | self.completedSeries = completedSeries 57 | } 58 | 59 | mutating func setUsersID(usersID: String) { 60 | self.usersID = usersID 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Fit Vein/Models/Post.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Post.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 28/10/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Post: Codable, Identifiable { 11 | var id: String 12 | var authorID: String 13 | var authorFirstName: String 14 | var authorUsername: String 15 | var authorProfilePictureURL: String 16 | var addDate: Date 17 | var text: String 18 | var reactionsUsersIDs: [String]? 19 | var commentedUsersIDs: [String]? 20 | var comments: [Comment]? 21 | var photoURL: String? 22 | 23 | init(authorID: String, authorFirstName: String, authorUsername: String, authorProfilePictureURL: String, text: String) { 24 | self.id = UUID().uuidString 25 | self.authorID = authorID 26 | self.authorFirstName = authorFirstName 27 | self.authorUsername = authorUsername 28 | self.authorProfilePictureURL = authorProfilePictureURL 29 | self.addDate = Date() 30 | self.text = text 31 | } 32 | 33 | init(id: String, authorID: String, authorFirstName: String, authorUsername: String, authorProfilePictureURL: String, addDate: Date, text: String, reactionsUsersIDs: [String]?, commentedUsersIDs: [String]?, comments: [Comment]?, photoURL: String? = nil) { 34 | self.id = id 35 | self.authorID = authorID 36 | self.authorFirstName = authorFirstName 37 | self.authorUsername = authorUsername 38 | self.authorProfilePictureURL = authorProfilePictureURL 39 | self.addDate = addDate 40 | self.text = text 41 | self.reactionsUsersIDs = reactionsUsersIDs 42 | self.commentedUsersIDs = commentedUsersIDs 43 | self.comments = comments 44 | self.photoURL = photoURL 45 | } 46 | } 47 | 48 | struct Comment: Codable, Identifiable { 49 | var id: String 50 | var authorID: String 51 | var postID: String 52 | var authorFirstName: String 53 | var authorUsername: String 54 | var authorProfilePictureURL: String 55 | var addDate: Date 56 | var reactionsUsersIDs: [String]? 57 | var text: String 58 | 59 | init(authorID: String, postID: String, authorFirstName: String, authorUsername: String, authorProfilePictureURL: String, text: String) { 60 | self.id = UUID().uuidString 61 | self.authorID = authorID 62 | self.postID = postID 63 | self.authorFirstName = authorFirstName 64 | self.authorUsername = authorUsername 65 | self.authorProfilePictureURL = authorProfilePictureURL 66 | self.addDate = Date() 67 | self.text = text 68 | } 69 | 70 | init(id: String, authorID: String, postID: String, authorFirstName: String, authorUsername: String, authorProfilePictureURL: String, addDate: Date, text: String, reactionsUsersIDs: [String]?) { 71 | self.id = id 72 | self.authorID = authorID 73 | self.postID = postID 74 | self.authorFirstName = authorFirstName 75 | self.authorUsername = authorUsername 76 | self.authorProfilePictureURL = authorProfilePictureURL 77 | self.addDate = addDate 78 | self.text = text 79 | self.reactionsUsersIDs = reactionsUsersIDs 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Fit Vein/Models/Profile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Profile.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 20/10/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Profile: Codable, Identifiable { 11 | var id: String 12 | var firstName: String 13 | var username: String 14 | var birthDate: Date 15 | var age: Int 16 | var country: String 17 | var language: String 18 | var gender: String 19 | var email: String 20 | var profilePictureURL: String? 21 | var followedIDs: [String]? 22 | var reactedPostsIDs: [String]? 23 | var commentedPostsIDs: [String]? 24 | var reactedCommentsIDs: [String]? 25 | var completedWorkouts: Int = 0 26 | var level: Int = 1 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/AddWorkoutView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/AddWorkoutView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/FinishedWorkoutView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/FinishedWorkoutView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/HealthTabView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/HealthTabView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/HomeTabView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/HomeTabView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/MedalView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/MedalView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/PostCommentsView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/PostCommentsView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/ProfileView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/ProfileView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/SearchFriendsView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/SearchFriendsView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/SettingsView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/SettingsView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/WorkoutTimerView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/WorkoutTimerView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/WorkoutsTabView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/WorkoutsTabView.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/WorkoutsTabViewList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/WorkoutsTabViewList.png -------------------------------------------------------------------------------- /Fit Vein/Onboarding Photos/WorkoutsView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljaniszewski00/Fit-Vein/d33c72da202ad617d8f4751ba9d638cea8da25fb/Fit Vein/Onboarding Photos/WorkoutsView.png -------------------------------------------------------------------------------- /Fit Vein/OnboardingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 24/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct OnboardingView: View { 11 | @Environment(\.dismiss) var dismiss 12 | @Environment(\.colorScheme) var colorScheme 13 | 14 | private var imagesNames: [String] = ["", "HomeTabView", "PostCommentsView", "SearchFriendsView", "WorkoutsView", "AddWorkoutView", "WorkoutTimerView", "FinishedWorkoutView", "ProfileView", "HealthTabView", "WorkoutsTabView", "WorkoutsTabViewList", "SettingsView", "MedalView"] 15 | 16 | private var titles: [String] = [String(localized: "Onboarding_welcome_title"), String(localized: "Onboarding_home_tab_view_title"), String(localized: "Onboarding_post_comments_title"), String(localized: "Onboarding_search_friends_view_title"), String(localized: "Onboarding_workouts_view_title"), String(localized: "Onboarding_add_workout_view_title"), String(localized: "Onboarding_workout_timer_view_title"), String(localized: "Onboarding_finished_workout_view_title"), String(localized: "Onboarding_profile_view_title"), String(localized: "Onboarding_health_tab_view_title"), String(localized: "Onboarding_workouts_tab_view_title"), String(localized: "Onboarding_workouts_tab_view_list_title"), String(localized: "Onboarding_settings_view_title"), String(localized: "Onboarding_medal_view_title"), String(localized: "Onboarding_thanks_title")] 17 | 18 | private var descriptions: [String] = [String(localized: "Onboarding_welcome_description"), String(localized: "Onboarding_home_tab_view_description"), String(localized: "Onboarding_post_comments_description"), String(localized: "Onboarding_search_friends_view_description"), String(localized: "Onboarding_workouts_view_description"), String(localized: "Onboarding_add_workout_view_description"), String(localized: "Onboarding_workout_timer_view_description"), String(localized: "Onboarding_finished_workout_view_description"), String(localized: "Onboarding_profile_view_description"), String(localized: "Onboarding_health_tab_view_description"), String(localized: "Onboarding_workouts_tab_view_description"), String(localized: "Onboarding_workouts_tab_view_list_description"), String(localized: "Onboarding_settings_view_description"), String(localized: "Onboarding_medal_view_description"), String(localized: "Onboarding_thanks_description")] 19 | 20 | var body: some View { 21 | GeometryReader { geometry in 22 | let screenWidth = geometry.size.width 23 | let screenHeight = geometry.size.height 24 | 25 | VStack { 26 | ZStack() { 27 | TabView { 28 | ForEach(0...imagesNames.count, id: \.self) { number in 29 | VStack { 30 | Spacer() 31 | 32 | switch number { 33 | case 0: 34 | LottieView(name: "hello", loopMode: .loop, contentMode: .scaleAspectFill) 35 | .frame(width: screenWidth * 0.7, height: screenHeight * 0.35) 36 | case 14: 37 | LottieView(name: "baboonExercising", loopMode: .loop, contentMode: .scaleAspectFill) 38 | .frame(width: screenWidth) 39 | default: 40 | Image(uiImage: UIImage(named: imagesNames[number])!) 41 | .resizable() 42 | .scaledToFill() 43 | .frame(width: screenWidth * 0.5, height: screenHeight * 0.5) 44 | } 45 | 46 | Spacer() 47 | 48 | VStack(alignment: .center) { 49 | Text(titles[number]) 50 | .font(.largeTitle) 51 | .bold() 52 | .padding(.bottom, screenHeight * 0.025) 53 | 54 | Text(descriptions[number]) 55 | .font(.system(size: number == 0 ? screenHeight * 0.03 : screenHeight * 0.02)) 56 | .padding(.bottom, screenHeight * 0.025) 57 | 58 | Spacer() 59 | } 60 | .padding() 61 | .frame(width: screenWidth, height: screenHeight * 0.3) 62 | .background(Color.accentColor) 63 | } 64 | } 65 | } 66 | .tabViewStyle(PageTabViewStyle()) 67 | } 68 | HStack { 69 | Button(action: { 70 | withAnimation { 71 | dismiss() 72 | } 73 | }, label: { 74 | ZStack { 75 | RoundedRectangle(cornerRadius: 10, style: .continuous) 76 | .foregroundColor(Color(uiColor: UIColor(red: 180, green: 255, blue: 180))) 77 | 78 | HStack { 79 | Text(String(localized: "Onboarding_get_started_button")) 80 | .fontWeight(.bold) 81 | .foregroundColor(Color(uiColor: UIColor(red: 100, green: 215, blue: 100))) 82 | } 83 | .padding(.horizontal) 84 | .frame(height: screenHeight * 0.05) 85 | } 86 | .frame(width: screenWidth * 0.4, height: screenHeight * 0.06) 87 | }) 88 | } 89 | .padding() 90 | .frame(width: screenWidth) 91 | .background(Color(uiColor: .systemGray6)) 92 | } 93 | } 94 | } 95 | } 96 | 97 | struct OnboardingView_Previews: PreviewProvider { 98 | static var previews: some View { 99 | OnboardingView() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Fit Vein/PreLaunchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreLaunchView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 03/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PreLaunchView: View { 11 | @EnvironmentObject var sessionStore: SessionStore 12 | @Environment(\.colorScheme) var colorScheme 13 | @State private var showContentView: Bool = false 14 | 15 | var body: some View { 16 | GeometryReader { geometry in 17 | let screenWidth = geometry.size.width 18 | let screenHeight = geometry.size.height 19 | 20 | Group { 21 | if showContentView { 22 | ContentView() 23 | .environmentObject(sessionStore) 24 | } else { 25 | VStack(alignment: .center) { 26 | Image(uiImage: UIImage(named: colorScheme == .dark ? "FitVeinIconDark" : "FitVeinIconLight")!) 27 | .resizable() 28 | .scaledToFill() 29 | .frame(width: screenWidth * 0.2, height: screenHeight * 0.2) 30 | .padding(.top, screenHeight * 0.15) 31 | 32 | HStack { 33 | Text("Fit") 34 | .foregroundColor(.accentColor) 35 | .fontWeight(.bold) 36 | Text("Vein") 37 | .fontWeight(.bold) 38 | } 39 | .font(.system(size: screenHeight * 0.1)) 40 | 41 | LottieView(name: "dumbleLoading", loopMode: .playOnce, contentMode: .scaleAspectFill) 42 | .frame(width: screenWidth * 0.6, height: screenHeight * 0.3) 43 | .padding(.top, screenHeight * 0.05) 44 | 45 | Spacer() 46 | } 47 | .frame(width: screenWidth) 48 | } 49 | } 50 | .onAppear { 51 | withAnimation(.linear.delay(2.5)) { 52 | showContentView = true 53 | } 54 | } 55 | 56 | } 57 | } 58 | } 59 | 60 | struct PreLaunchView_Previews: PreviewProvider { 61 | static var previews: some View { 62 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 63 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 64 | PreLaunchView() 65 | .preferredColorScheme(colorScheme) 66 | .previewDevice(PreviewDevice(rawValue: deviceName)) 67 | .previewDisplayName(deviceName) 68 | .environmentObject(SessionStore(forPreviews: true)) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Fit Vein/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fit Vein/ViewModels/HealthKitViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitViewModel.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 24/11/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import HealthKit 11 | 12 | @MainActor 13 | class HealthKitViewModel: ObservableObject { 14 | private var healthKitRepository = HealthKitRepository() 15 | @Published var stepCount = [HealthStat]() 16 | @Published var activeEnergyBurned = [HealthStat]() 17 | @Published var distanceWalkingRunning = [HealthStat]() 18 | @Published var appleExerciseTime = [HealthStat]() 19 | @Published var heartRate = [HealthStat]() 20 | 21 | init() { 22 | self.healthKitRepository.requestAuthorization() { success in 23 | self.healthKitRepository.requestHealthStats(by: "stepCount") { hStats in 24 | DispatchQueue.main.async { 25 | self.stepCount = hStats 26 | } 27 | } 28 | 29 | self.healthKitRepository.requestHealthStats(by: "activeEnergyBurned") { hStats in 30 | DispatchQueue.main.async { 31 | self.activeEnergyBurned = hStats 32 | } 33 | } 34 | 35 | self.healthKitRepository.requestHealthStats(by: "distanceWalkingRunning") { hStats in 36 | DispatchQueue.main.async { 37 | self.distanceWalkingRunning = hStats 38 | } 39 | } 40 | 41 | self.healthKitRepository.requestHealthStats(by: "appleExerciseTime") { hStats in 42 | DispatchQueue.main.async { 43 | self.appleExerciseTime = hStats 44 | } 45 | } 46 | 47 | self.healthKitRepository.requestHealthStats(by: "heartRate") { hStats in 48 | DispatchQueue.main.async { 49 | self.heartRate = hStats 50 | } 51 | } 52 | } 53 | } 54 | 55 | let measurementFormatter = MeasurementFormatter() 56 | 57 | func value(from stat: HKQuantity?) -> (value: Float, units: String) { 58 | guard let stat = stat else { 59 | return (0, "-") 60 | } 61 | 62 | measurementFormatter.unitStyle = .short 63 | 64 | if stat.is(compatibleWith: .kilocalorie()) { 65 | let value = stat.doubleValue(for: .kilocalorie()) 66 | return(Float(value), stat.description.letters) 67 | } else if stat.is(compatibleWith: .meter()) { 68 | let value = stat.doubleValue(for: .mile()) 69 | let unit = Measurement(value: value, unit: UnitLength.miles) 70 | return (Float(round(value * 100) / 100.0), measurementFormatter.string(from: unit).letters) 71 | } else if stat.is(compatibleWith: .count()) { 72 | let value = stat.doubleValue(for: .count()) 73 | return (Float(value), stat.description.letters == "count" ? "" : stat.description.letters) 74 | } else if stat.is(compatibleWith: .minute()) { 75 | let value = stat.doubleValue(for: .minute()) 76 | return (Float(value), stat.description.letters) 77 | } else { 78 | let value = stat.doubleValue(for: HKUnit(from: "count/min")) 79 | return (Float(value), "BPM") 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Fit Vein/ViewModels/MedalsViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MedalsViewModel.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 29/01/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | @MainActor 11 | class MedalsViewModel: ObservableObject { 12 | var sessionStore = SessionStore(forPreviews: false) 13 | private let firestoreManager = FirestoreManager() 14 | 15 | private(set) var allMedalsDescriptions: [String: String] = [ 16 | "medalFirstLevel": String(localized: "ProfileView_medal_first_level"), 17 | "medalSecondLevel": String(localized: "ProfileView_medal_second_level"), 18 | "medalThirdLevel": String(localized: "ProfileView_medal_third_level"), 19 | "medalFirstPost": String(localized: "ProfileView_medal_first_post"), 20 | "medalFirstComment": String(localized: "ProfileView_medal_first_comment"), 21 | "medalFirstLike": String(localized: "ProfileView_medal_first_like"), 22 | "medalFirstWorkout": String(localized: "ProfileView_medal_first_workout"), 23 | "medalFirstOwnWorkout": String(localized: "ProfileView_medal_first_own_workout")] 24 | 25 | @Published var allUsersMedals: [String] = [] 26 | 27 | init() { 28 | fetchUsersMedals() 29 | } 30 | 31 | func fetchUsersMedals() { 32 | if let currentUser = sessionStore.currentUser { 33 | firestoreManager.fetchDataForMedalsViewModel(userID: currentUser.uid) { medals in 34 | self.allUsersMedals = medals.sorted(by: <) 35 | } 36 | } 37 | } 38 | 39 | func giveUserMedal(medalName: String) { 40 | if let currentUser = sessionStore.currentUser { 41 | if !allUsersMedals.contains(medalName) { 42 | firestoreManager.giveUserMedal(userID: currentUser.uid, medalName: medalName) { success in } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Fit Vein/ViewModels/SignInViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignInViewModel.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 13/10/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | @MainActor 11 | class SignInViewModel: ObservableObject { 12 | var sessionStore = SessionStore(forPreviews: false) 13 | private let firestoreManager = FirestoreManager() 14 | private let firebaseStorageManager = FirebaseStorageManager() 15 | 16 | func signIn(email: String, password: String, completion: @escaping ((Bool) -> ())) { 17 | self.sessionStore.signIn(email: email, password: password) { success in 18 | completion(success) 19 | } 20 | } 21 | 22 | func sendRecoveryEmail(email: String, completion: @escaping ((Bool) -> ())) { 23 | self.sessionStore.sendRecoveryEmail(email: email) { success in 24 | completion(success) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Fit Vein/ViewModels/SignUpViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpViewModel.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 13/10/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import SwiftUI 11 | 12 | @MainActor 13 | class SignUpViewModel: ObservableObject { 14 | var sessionStore = SessionStore(forPreviews: false) 15 | private let firestoreManager = FirestoreManager() 16 | private let firebaseStorageManager = FirebaseStorageManager() 17 | 18 | func signUp(firstName: String, userName: String, birthDate: Date, country: String, language: String, email: String, password: String, gender: String, completion: @escaping ((Bool) -> ())) { 19 | self.sessionStore.signUp(email: email, password: password) { (userID, success) in 20 | if success { 21 | if let userID = userID { 22 | self.firestoreManager.signUpDataCreation(id: userID, firstName: firstName, username: userName, birthDate: birthDate, country: country, language: language, email: email, gender: gender) { profile, success in 23 | completion(success) 24 | } 25 | } else { 26 | completion(false) 27 | } 28 | } else { 29 | completion(false) 30 | } 31 | } 32 | } 33 | 34 | func checkUsernameDuplicate(username: String) async throws -> Bool { 35 | try await firestoreManager.checkUsernameDuplicate(username: username) 36 | } 37 | 38 | func checkEmailDuplicate(email: String) async throws -> Bool { 39 | try await firestoreManager.checkEmailDuplicate(email: email) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Fit Vein/ViewModels/WorkoutViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutViewModel.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 20/10/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | @MainActor 12 | class WorkoutViewModel: ObservableObject { 13 | var sessionStore = SessionStore(forPreviews: false) 14 | @Published var workout: IntervalWorkout? 15 | @Published var workoutsList: [IntervalWorkout] = [IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 200, series: 8, workTime: 45, restTime: 15), 16 | IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 260, series: 10, workTime: 30, restTime: 15), 17 | IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 140, series: 5, workTime: 60, restTime: 30), 18 | IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 110, series: 7, workTime: 45, restTime: 20), 19 | IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 260, series: 8, workTime: 45, restTime: 20)] 20 | @AppStorage("usersWorkoutsList") var usersWorkoutsList: [IntervalWorkout] = [] 21 | private let firestoreManager = FirestoreManager() 22 | private let firebaseStorageManager = FirebaseStorageManager() 23 | 24 | init(forPreviews: Bool) { 25 | if forPreviews { 26 | self.workout = IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 0, series: 8, workTime: 45, restTime: 15) 27 | } 28 | } 29 | 30 | func setup(sessionStore: SessionStore) { 31 | self.sessionStore = sessionStore 32 | } 33 | 34 | func startWorkout(workout: IntervalWorkout) { 35 | self.workout = workout 36 | } 37 | 38 | func stopWorkout(calories: Int, completedDuration: Int, completedSeries: Int) { 39 | self.workout?.setDataOnEnd(calories: calories, completedDuration: completedDuration, completedSeries: completedSeries) 40 | } 41 | 42 | func saveWorkoutToDatabase(completion: @escaping ((Bool) -> ())) { 43 | if self.workout != nil { 44 | if self.sessionStore.currentUser != nil { 45 | self.workout!.setUsersID(usersID: self.sessionStore.currentUser!.uid) 46 | self.firestoreManager.workoutDataCreation(id: workout!.id, usersID: workout!.usersID, type: workout!.type, date: workout!.date, isFinished: workout!.isFinished, calories: workout!.calories, series: workout!.series, workTime: workout!.workTime, restTime: workout!.restTime, completedDuration: workout!.completedDuration, completedSeries: workout!.completedSeries) { success in 47 | if success { 48 | print("Successfully saved workout to the database.") 49 | completion(true) 50 | } else { 51 | print("Error saving workout to the database.") 52 | completion(false) 53 | } 54 | } 55 | } else { 56 | completion(false) 57 | print("Error saving workout to the database: current user is nil") 58 | } 59 | } 60 | } 61 | 62 | func addUserWorkout(series: Int, workTime: Int, restTime: Int) { 63 | self.usersWorkoutsList.append(IntervalWorkout(id: UUID().uuidString, usersID: "9999", type: "Interval", date: Date(), isFinished: false, calories: 200, series: series, workTime: workTime, restTime: restTime)) 64 | } 65 | 66 | func deleteUserWorkout(indexSet: IndexSet) { 67 | self.usersWorkoutsList.remove(atOffsets: indexSet) 68 | } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /Fit Vein/Views/HelpViews/CustomTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTextField.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 11/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CustomTextField: View { 11 | var isSecureField: Bool = false 12 | var textFieldProperty: String 13 | var textFieldImageName: String 14 | var textFieldSignsLimit: Int = 0 15 | @Binding var text: String 16 | @Binding var isFocusedParentView: Bool 17 | @FocusState private var isFocused: Bool 18 | 19 | var body: some View { 20 | VStack { 21 | VStack(alignment: .leading, spacing: 4) { 22 | HStack(spacing: 15) { 23 | Group { 24 | if !isSecureField { 25 | TextField("", text: $text) 26 | .onTapGesture { 27 | withAnimation(.easeIn) { 28 | isFocusedParentView = true 29 | isFocused = true 30 | } 31 | } 32 | .onSubmit { 33 | withAnimation(.easeOut) { 34 | isFocused = false 35 | isFocusedParentView = false 36 | } 37 | } 38 | } else { 39 | SecureField("", text: $text) 40 | .onTapGesture { 41 | withAnimation(.easeIn) { 42 | isFocusedParentView = true 43 | isFocused = true 44 | } 45 | } 46 | .onSubmit { 47 | withAnimation(.easeOut) { 48 | isFocused = false 49 | isFocusedParentView = false 50 | } 51 | } 52 | } 53 | } 54 | .focused($isFocused) 55 | .disableAutocorrection(true) 56 | .autocapitalization(.none) 57 | 58 | Image(systemName: textFieldImageName) 59 | .foregroundColor(isFocused ? .accentColor : .gray) 60 | } 61 | .padding(.top, isFocused ? 15 : 0) 62 | .background( 63 | Text(textFieldProperty) 64 | .scaleEffect(text.isEmpty ? (isFocused ? 0.8 : 1) : (isFocused ? 0.8 : 0.8)) 65 | .offset(x: text.isEmpty ? (isFocused ? -7 : 0) : -7, y: text.isEmpty ? (isFocused ? -20 : 0) : -20) 66 | .foregroundColor(isFocused ? .accentColor : .gray) 67 | 68 | ,alignment: .leading 69 | ) 70 | .padding(.horizontal) 71 | 72 | Rectangle() 73 | .fill(isFocused ? Color.accentColor : Color.gray) 74 | .opacity(isFocused ? 1 : 0.5) 75 | .frame(height: 1) 76 | } 77 | .padding(.vertical, isFocused ? 18 : (text.isEmpty ? 12 : 20)) 78 | .background(Color.gray.opacity(0.09)) 79 | .cornerRadius(5) 80 | 81 | HStack { 82 | Spacer() 83 | 84 | if textFieldSignsLimit != 0 { 85 | Text("\(text.count)/\(textFieldSignsLimit)") 86 | .font(.caption) 87 | .foregroundColor(text.count > textFieldSignsLimit ? .red : .gray) 88 | .padding(.trailing) 89 | .padding(.top, 4) 90 | } 91 | } 92 | } 93 | .padding() 94 | } 95 | } 96 | 97 | struct CustomTextField_Previews: PreviewProvider { 98 | static var previews: some View { 99 | PreviewWrapper() 100 | } 101 | 102 | struct PreviewWrapper: View { 103 | @State(initialValue: "") var text: String 104 | @State(initialValue: false) var isFocused: Bool 105 | 106 | var body: some View { 107 | CustomTextField(textFieldProperty: "Username", textFieldImageName: "", textFieldSignsLimit: 15, text: $text, isFocusedParentView: $isFocused) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Fit Vein/Views/HelpViews/ProfileTabFetchingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileTabFetchingView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 23/11/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProfileTabFetchingView: View { 11 | var body: some View { 12 | GeometryReader { geometry in 13 | let screenWidth = geometry.size.width 14 | let screenHeight = geometry.size.height 15 | 16 | VStack { 17 | HStack { 18 | RoundedRectangle(cornerRadius: 50) 19 | .foregroundColor(Color(uiColor: .systemGray6)) 20 | .frame(width: screenWidth * 0.4, height: screenHeight * 0.2) 21 | 22 | VStack { 23 | HStack { 24 | RoundedRectangle(cornerRadius: 25) 25 | .foregroundColor(Color(uiColor: .systemGray6)) 26 | .frame(width: screenWidth * 0.4, height: screenHeight * 0.03) 27 | 28 | Spacer() 29 | } 30 | .padding(.top, screenHeight * 0.02) 31 | 32 | HStack { 33 | RoundedRectangle(cornerRadius: 25) 34 | .foregroundColor(Color(uiColor: .systemGray6)) 35 | .frame(width: screenWidth * 0.4, height: screenHeight * 0.03) 36 | 37 | Spacer() 38 | } 39 | 40 | Spacer() 41 | } 42 | .frame(width: screenWidth * 0.5, height: screenHeight * 0.2) 43 | } 44 | .padding() 45 | 46 | VStack(spacing: screenHeight * 0.04) { 47 | RoundedRectangle(cornerRadius: 25) 48 | .foregroundColor(Color(uiColor: .systemGray6)) 49 | .frame(width: screenWidth * 0.9, height: screenHeight * 0.03) 50 | 51 | RoundedRectangle(cornerRadius: 25) 52 | .foregroundColor(Color(uiColor: .systemGray6)) 53 | .frame(width: screenWidth * 0.35, height: screenHeight * 0.02) 54 | 55 | HStack { 56 | Spacer() 57 | 58 | RoundedRectangle(cornerRadius: 25) 59 | .foregroundColor(Color(uiColor: .systemGray6)) 60 | .frame(width: screenWidth * 0.3, height: screenHeight * 0.03) 61 | 62 | Spacer() 63 | 64 | RoundedRectangle(cornerRadius: 25) 65 | .foregroundColor(Color(uiColor: .systemGray6)) 66 | .frame(width: screenWidth * 0.3, height: screenHeight * 0.03) 67 | 68 | Spacer() 69 | } 70 | .padding() 71 | 72 | HStack { 73 | RoundedRectangle(cornerRadius: 25) 74 | .foregroundColor(Color(uiColor: .systemGray6)) 75 | .frame(width: screenWidth * 0.5, height: screenHeight * 0.05) 76 | 77 | Spacer() 78 | } 79 | 80 | LazyVGrid(columns: [GridItem(.flexible()), 81 | GridItem(.flexible())], spacing: 0) { 82 | ForEach(0..<5) { _ in 83 | RoundedRectangle(cornerRadius: 25) 84 | .frame(height: screenHeight * 0.2) 85 | .foregroundColor(Color(uiColor: .systemGray6)) 86 | .padding() 87 | } 88 | } 89 | } 90 | .padding() 91 | } 92 | 93 | } 94 | } 95 | } 96 | 97 | struct ProfileTabFetchingView_Previews: PreviewProvider { 98 | static var previews: some View { 99 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 100 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 101 | 102 | ProfileTabFetchingView() 103 | .preferredColorScheme(colorScheme) 104 | .previewDevice(PreviewDevice(rawValue: deviceName)) 105 | .previewDisplayName(deviceName) 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Fit Vein/Views/HelpViews/RingProgressViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RingProgressViewStyle.swift 3 | // Fit Vein 4 | // 5 | // Created by SwiftUI Recipes on 12/08/2021. 6 | // https://swiftuirecipes.com 7 | // https://github.com/globulus/swiftui-circle-progress-bar 8 | // 9 | 10 | import SwiftUI 11 | 12 | public struct RingProgressViewStyle: ProgressViewStyle { 13 | private let defaultSize: CGFloat = 36 14 | private let lineWidth: CGFloat = 6 15 | private let defaultProgress = 0.2 // CHANGE 16 | 17 | // tracks the rotation angle for the indefinite progress bar 18 | @State private var fillRotationAngle = Angle.degrees(-90) // ADD 19 | 20 | public func makeBody(configuration: ProgressViewStyleConfiguration) -> some View { 21 | VStack { 22 | configuration.label 23 | progressCircleView(fractionCompleted: configuration.fractionCompleted ?? defaultProgress, 24 | isIndefinite: configuration.fractionCompleted == nil) // UPDATE 25 | configuration.currentValueLabel 26 | } 27 | } 28 | 29 | private func progressCircleView(fractionCompleted: Double, 30 | isIndefinite: Bool) -> some View { // UPDATE 31 | // this is the circular "track", which is a full circle at all times 32 | Circle() 33 | .strokeBorder(Color.gray.opacity(0.5), lineWidth: lineWidth, antialiased: true) 34 | .overlay(fillView(fractionCompleted: fractionCompleted, isIndefinite: isIndefinite)) // UPDATE 35 | .frame(width: defaultSize, height: defaultSize) 36 | } 37 | 38 | private func fillView(fractionCompleted: Double, 39 | isIndefinite: Bool) -> some View { // UPDATE 40 | Circle() // the fill view is also a circle 41 | .trim(from: 0, to: CGFloat(fractionCompleted)) 42 | .stroke(Color.secondary, lineWidth: lineWidth) 43 | .frame(width: defaultSize - lineWidth, height: defaultSize - lineWidth) 44 | .rotationEffect(fillRotationAngle) // UPDATE 45 | // triggers the infinite rotation animation for indefinite progress views 46 | .onAppear { 47 | if isIndefinite { 48 | withAnimation(.easeInOut(duration: 1).repeatForever()) { 49 | fillRotationAngle = .degrees(270) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Fit Vein/Views/HomeTabViews/CommentsViews/HomeTabCommentsViewPostView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeTabCommentsViewPostView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 13/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeTabCommentsViewPostView: View { 11 | @EnvironmentObject private var homeViewModel: HomeViewModel 12 | @EnvironmentObject private var profileViewModel: ProfileViewModel 13 | @EnvironmentObject private var medalsViewModel: MedalsViewModel 14 | @Environment(\.colorScheme) var colorScheme 15 | 16 | private var post: Post 17 | 18 | init(post: Post) { 19 | self.post = post 20 | } 21 | 22 | var body: some View { 23 | let screenWidth = UIScreen.screenWidth 24 | let screenHeight = UIScreen.screenHeight 25 | 26 | VStack { 27 | Text(post.text) 28 | .fixedSize(horizontal: false, vertical: true) 29 | .padding() 30 | .padding(.top) 31 | 32 | if let post = self.homeViewModel.getCurrentPostDetails(postID: post.id) { 33 | if let postPhotoURL = post.photoURL { 34 | Group { 35 | if let postPictureURL = self.homeViewModel.postsPicturesURLs[post.id] { 36 | AsyncImage(url: postPictureURL) { phase in 37 | if let image = phase.image { 38 | image 39 | .resizable() 40 | .scaledToFill() 41 | } else { 42 | ProgressView() 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | HStack { 51 | if post.reactionsUsersIDs != nil { 52 | if post.reactionsUsersIDs!.count != 0 { 53 | Image(systemName: post.reactionsUsersIDs!.contains(self.profileViewModel.profile!.id) ? "hand.thumbsup.fill" : "hand.thumbsup") 54 | .foregroundColor(.accentColor) 55 | .padding(.leading, screenWidth * 0.05) 56 | 57 | Text("\(post.reactionsUsersIDs!.count)") 58 | .foregroundColor(Color(uiColor: .systemGray2)) 59 | } 60 | 61 | } 62 | 63 | Spacer() 64 | 65 | if let postComments = homeViewModel.postsComments[post.id] { 66 | Text("\(postComments.count) \(String(localized: "CommentView_comment_number_label"))") 67 | .foregroundColor(Color(uiColor: .systemGray2)) 68 | .padding(.trailing, screenWidth * 0.05) 69 | } 70 | } 71 | .padding(.top, screenHeight * 0.02) 72 | 73 | HStack { 74 | Spacer() 75 | 76 | if let reactionsUsersIDs = profileViewModel.profile!.reactedPostsIDs { 77 | Button(action: { 78 | withAnimation { 79 | if reactionsUsersIDs.contains(post.id) { 80 | self.homeViewModel.removeReactionFromPost(postID: post.id) { success in } 81 | } else { 82 | self.homeViewModel.reactToPost(postID: post.id) { success in 83 | self.medalsViewModel.giveUserMedal(medalName: "medalFirstLike") 84 | } 85 | } 86 | } 87 | }, label: { 88 | HStack { 89 | Image(systemName: "hand.thumbsup") 90 | .symbolVariant(reactionsUsersIDs.contains(post.id) ? .fill : .none) 91 | Text(String(localized: "CommentView_post_like_button")) 92 | } 93 | .foregroundColor(reactionsUsersIDs.contains(post.id) ? .accentColor : (colorScheme == .dark ? .white : .black)) 94 | }) 95 | } else { 96 | Button(action: { 97 | withAnimation { 98 | self.homeViewModel.reactToPost(postID: post.id) { success in 99 | self.medalsViewModel.giveUserMedal(medalName: "medalFirstLike") 100 | } 101 | } 102 | }, label: { 103 | HStack { 104 | Image(systemName: "hand.thumbsup") 105 | Text(String(localized: "CommentView_post_like_button")) 106 | } 107 | .foregroundColor(colorScheme == .dark ? .white : .black) 108 | }) 109 | } 110 | 111 | Spacer() 112 | } 113 | .padding(screenHeight * 0.014) 114 | .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 10)) 115 | 116 | Divider() 117 | } 118 | } 119 | } 120 | 121 | struct HomeTabCommentsViewPostView_Previews: PreviewProvider { 122 | static var previews: some View { 123 | let homeViewModel = HomeViewModel(forPreviews: true) 124 | let profileViewModel = ProfileViewModel(forPreviews: true) 125 | let comments = [Comment(id: "id1", authorID: "1", postID: "1", authorFirstName: "Maciej", authorUsername: "maciej.j223", authorProfilePictureURL: "nil", addDate: Date(), text: "Good job!", reactionsUsersIDs: ["2", "3"]), Comment(id: "id2", authorID: "3", postID: "1", authorFirstName: "Kamil", authorUsername: "kamil.j223", authorProfilePictureURL: "nil", addDate: Date(), text: "Let's Go!", reactionsUsersIDs: ["1", "3"])] 126 | let post = Post(id: "1", authorID: "1", authorFirstName: "Jan", authorUsername: "jan23.d", authorProfilePictureURL: "", addDate: Date(), text: "Did this today!", reactionsUsersIDs: nil, commentedUsersIDs: nil, comments: comments) 127 | 128 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 129 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 130 | NavigationView { 131 | HomeTabCommentsViewPostView(post: post) 132 | .environmentObject(homeViewModel) 133 | .environmentObject(profileViewModel) 134 | .preferredColorScheme(colorScheme) 135 | .previewDevice(PreviewDevice(rawValue: deviceName)) 136 | .previewDisplayName(deviceName) 137 | } 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Fit Vein/Views/HomeTabViews/HomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 20/10/2021. 6 | // 7 | 8 | 9 | import SwiftUI 10 | 11 | struct HomeView: View { 12 | @EnvironmentObject private var homeViewModel: HomeViewModel 13 | @EnvironmentObject private var profileViewModel: ProfileViewModel 14 | @EnvironmentObject private var medalsViewModel: MedalsViewModel 15 | @EnvironmentObject private var networkManager: NetworkManager 16 | @Environment(\.colorScheme) var colorScheme 17 | 18 | var body: some View { 19 | let screenWidth = UIScreen.screenWidth 20 | let screenHeight = UIScreen.screenHeight 21 | 22 | NavigationView { 23 | Group { 24 | if !networkManager.isConnected { 25 | VStack { 26 | Spacer() 27 | HStack { 28 | Spacer() 29 | LottieView(name: "noInternetConnection", loopMode: .loop) 30 | .frame(width: screenWidth * 0.7, height: screenHeight * 0.7) 31 | Spacer() 32 | } 33 | Spacer() 34 | } 35 | } else { 36 | if profileViewModel.profile != nil { 37 | withAnimation { 38 | ScrollView(.vertical, showsIndicators: false) { 39 | HomeTabSubViewShareView().environmentObject(homeViewModel).environmentObject(profileViewModel).environmentObject(medalsViewModel) 40 | 41 | Group { 42 | if let posts = homeViewModel.posts { 43 | if posts.count != 0 { 44 | LazyVStack { 45 | ForEach(posts) { post in 46 | HomeTabSubViewPostsView(post: post).environmentObject(homeViewModel).environmentObject(profileViewModel) 47 | .environmentObject(medalsViewModel) 48 | .background(Color(uiColor: .systemGray6), in: RoundedRectangle(cornerRadius: 10)) 49 | } 50 | } 51 | .padding(.bottom, screenHeight * 0.07) 52 | } else { 53 | VStack { 54 | Text(String(localized: "HomeView_nothing_to_present")) 55 | .fontWeight(.bold) 56 | .foregroundColor(.accentColor) 57 | .padding(.top, screenHeight * 0.07) 58 | 59 | Spacer() 60 | } 61 | } 62 | } else { 63 | if let followedIDs = self.profileViewModel.profile!.followedIDs { 64 | if followedIDs.count != 0 { 65 | // HomeTabPostsFetchingView() 66 | // .frame(width: screenWidth, height: screenHeight) 67 | VStack { 68 | Text(String(localized: "HomeView_nothing_to_present")) 69 | .fontWeight(.bold) 70 | .foregroundColor(.accentColor) 71 | .padding(.top, screenHeight * 0.07) 72 | 73 | Spacer() 74 | } 75 | } else { 76 | VStack { 77 | Text(String(localized: "HomeView_add_friends_label")) 78 | .fontWeight(.bold) 79 | .foregroundColor(.accentColor) 80 | .padding(.top, screenHeight * 0.07) 81 | 82 | Spacer() 83 | } 84 | } 85 | } else { 86 | VStack { 87 | Text(String(localized: "HomeView_add_friends_label")) 88 | .fontWeight(.bold) 89 | .foregroundColor(.accentColor) 90 | .padding(.top, screenHeight * 0.07) 91 | 92 | Spacer() 93 | } 94 | } 95 | } 96 | } 97 | } 98 | .padding(.top, screenHeight * 0.001) 99 | } 100 | } 101 | } 102 | } 103 | .navigationTitle("") 104 | .navigationBarTitleDisplayMode(.inline) 105 | .toolbar { 106 | ToolbarItem(placement: .navigationBarLeading) { 107 | Image(uiImage: UIImage(named: colorScheme == .dark ? "FitVeinIconDark" : "FitVeinIconLight")!) 108 | .resizable() 109 | .scaledToFit() 110 | .frame(width: screenWidth * 0.1, height: screenHeight * 0.1) 111 | } 112 | 113 | ToolbarItem(placement: .navigationBarTrailing) { 114 | NavigationLink(destination: SearchFriendsView().environmentObject(homeViewModel).environmentObject(profileViewModel).ignoresSafeArea(.keyboard)) { 115 | Image(systemName: "magnifyingglass") 116 | .foregroundColor(.accentColor) 117 | } 118 | .disabled(!networkManager.isConnected) 119 | } 120 | 121 | ToolbarItem(placement: .navigationBarTrailing) { 122 | NavigationLink(destination: NotificationsView()) { 123 | Image(systemName: "bell") 124 | .foregroundColor(.accentColor) 125 | } 126 | .disabled(!networkManager.isConnected) 127 | } 128 | } 129 | } 130 | .navigationViewStyle(.stack) 131 | } 132 | } 133 | 134 | struct HomeView_Previews: PreviewProvider { 135 | static var previews: some View { 136 | let homeViewModel = HomeViewModel(forPreviews: true) 137 | let profileViewModel = ProfileViewModel(forPreviews: true) 138 | 139 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 140 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 141 | HomeView() 142 | .environmentObject(homeViewModel) 143 | .environmentObject(profileViewModel) 144 | .preferredColorScheme(colorScheme) 145 | .previewDevice(PreviewDevice(rawValue: deviceName)) 146 | .previewDisplayName(deviceName) 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Fit Vein/Views/HomeTabViews/NotificationsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationsView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 28/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NotificationsView: View { 11 | var body: some View { 12 | GeometryReader { geometry in 13 | let screenWidth = geometry.size.width 14 | let screenHeight = geometry.size.height 15 | 16 | NavigationView { 17 | VStack { 18 | HStack { 19 | Spacer() 20 | 21 | LottieView(name: "notifications", loopMode: .loop) 22 | .frame(width: screenWidth * 0.25, height: screenHeight * 0.15) 23 | } 24 | .padding() 25 | .padding(.trailing, screenWidth * 0.05) 26 | 27 | HStack { 28 | Spacer() 29 | LottieView(name: "skeleton", loopMode: .loop) 30 | .frame(width: screenWidth, height: screenHeight * 0.5) 31 | Spacer() 32 | } 33 | Spacer() 34 | } 35 | .navigationTitle(String(localized: "NotificationsView_navigation_title")) 36 | } 37 | .navigationViewStyle(.stack) 38 | } 39 | } 40 | } 41 | 42 | struct NotificationsView_Previews: PreviewProvider { 43 | static var previews: some View { 44 | NotificationsView() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Fit Vein/Views/HomeTabViews/PostsViews/HomeTabSubViewPostDetailsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeTabSubViewPostDetailsView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 08/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeTabSubViewPostDetailsView: View { 11 | @EnvironmentObject private var homeViewModel: HomeViewModel 12 | @EnvironmentObject private var profileViewModel: ProfileViewModel 13 | 14 | @State private var showPostOptions = false 15 | @State private var showEditPostSheet = false 16 | 17 | private var currentUserID: String 18 | private var post: Post 19 | 20 | init(currentUserID: String, post: Post) { 21 | self.currentUserID = currentUserID 22 | self.post = post 23 | } 24 | 25 | var body: some View { 26 | let screenWidth = UIScreen.screenWidth 27 | let screenHeight = UIScreen.screenHeight 28 | 29 | VStack { 30 | HStack { 31 | Group { 32 | if let profilePictureURL = self.homeViewModel.postsAuthorsProfilePicturesURLs[post.id] { 33 | AsyncImage(url: profilePictureURL) { phase in 34 | if let image = phase.image { 35 | image 36 | .resizable() 37 | } else { 38 | Image(uiImage: UIImage(named: "blank-profile-hi")!) 39 | .resizable() 40 | } 41 | } 42 | } else { 43 | Image(uiImage: UIImage(named: "blank-profile-hi")!) 44 | .resizable() 45 | } 46 | } 47 | .aspectRatio(contentMode: .fill) 48 | .clipShape(RoundedRectangle(cornerRadius: 50)) 49 | .frame(width: screenWidth * 0.06, height: screenHeight * 0.06) 50 | .padding(.horizontal) 51 | 52 | VStack(spacing: screenHeight * 0.005) { 53 | HStack { 54 | Text(post.authorFirstName) 55 | .fontWeight(.bold) 56 | Text("•") 57 | Text(post.authorUsername) 58 | Spacer() 59 | 60 | if currentUserID == post.authorID { 61 | Button(action: { 62 | withAnimation { 63 | showPostOptions.toggle() 64 | } 65 | }, label: { 66 | Image(systemName: "ellipsis") 67 | .foregroundColor(.accentColor) 68 | }) 69 | 70 | } 71 | } 72 | 73 | HStack { 74 | Text(getShortDate(longDate: post.addDate)) 75 | .font(.system(size: screenHeight * 0.016)) 76 | .foregroundColor(Color(uiColor: .systemGray2)) 77 | Spacer() 78 | } 79 | } 80 | .padding(.horizontal) 81 | } 82 | .padding() 83 | 84 | Text(post.text) 85 | .fixedSize(horizontal: false, vertical: true) 86 | .padding(.horizontal) 87 | .padding(.bottom) 88 | 89 | if let post = self.homeViewModel.getCurrentPostDetails(postID: post.id) { 90 | if let postPhotoURL = post.photoURL { 91 | Group { 92 | if let postPictureURL = self.homeViewModel.postsPicturesURLs[post.id] { 93 | AsyncImage(url: postPictureURL) { phase in 94 | if let image = phase.image { 95 | image 96 | .resizable() 97 | .scaledToFill() 98 | } else { 99 | ProgressView() 100 | } 101 | } 102 | } 103 | } 104 | .padding(.bottom) 105 | } 106 | } 107 | } 108 | .confirmationDialog(String(localized: "HomeView_confirmation_dialog_text"), isPresented: $showPostOptions, titleVisibility: .visible) { 109 | Button(String(localized: "HomeView_confirmation_dialog_edit")) { 110 | showEditPostSheet.toggle() 111 | } 112 | 113 | Button(String(localized: "HomeView_confirmation_dialog_delete"), role: .destructive) { 114 | self.homeViewModel.deletePost(postID: post.id, postPictureURL: post.photoURL) { success in } 115 | } 116 | 117 | Button(String(localized: "HomeView_confirmation_dialog_cancel"), role: .cancel) {} 118 | } 119 | .sheet(isPresented: $showEditPostSheet) { 120 | EditPostView(post: post).environmentObject(homeViewModel).environmentObject(profileViewModel).ignoresSafeArea(.keyboard) 121 | } 122 | } 123 | } 124 | 125 | struct HomeTabSubViewPostDetailsView_Previews: PreviewProvider { 126 | static var previews: some View { 127 | let homeViewModel = HomeViewModel(forPreviews: true) 128 | let profileViewModel = ProfileViewModel(forPreviews: true) 129 | let comments = [Comment(id: "id1", authorID: "1", postID: "1", authorFirstName: "Maciej", authorUsername: "maciej.j223", authorProfilePictureURL: "nil", addDate: Date(), text: "Good job!", reactionsUsersIDs: ["2", "3"]), Comment(id: "id2", authorID: "3", postID: "1", authorFirstName: "Kamil", authorUsername: "kamil.j223", authorProfilePictureURL: "nil", addDate: Date(), text: "Let's Go!", reactionsUsersIDs: ["1", "3"])] 130 | let post = Post(id: "1", authorID: "1", authorFirstName: "Jan", authorUsername: "jan23.d", authorProfilePictureURL: "", addDate: Date(), text: "Did this today!", reactionsUsersIDs: nil, commentedUsersIDs: nil, comments: comments) 131 | 132 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 133 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 134 | HomeTabSubViewPostDetailsView(currentUserID: "id1", post: post) 135 | .environmentObject(homeViewModel) 136 | .environmentObject(profileViewModel) 137 | .preferredColorScheme(colorScheme) 138 | .previewDevice(PreviewDevice(rawValue: deviceName)) 139 | .previewDisplayName(deviceName) 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Fit Vein/Views/HomeTabViews/PostsViews/HomeTabSubViewPostsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeTabSubViewPostsView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 08/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeTabSubViewPostsView: View { 11 | @EnvironmentObject private var homeViewModel: HomeViewModel 12 | @EnvironmentObject private var profileViewModel: ProfileViewModel 13 | @EnvironmentObject private var medalsViewModel: MedalsViewModel 14 | 15 | @Environment(\.colorScheme) var colorScheme 16 | 17 | private var post: Post 18 | 19 | init(post: Post) { 20 | self.post = post 21 | } 22 | 23 | var body: some View { 24 | let screenWidth = UIScreen.screenWidth 25 | let screenHeight = UIScreen.screenHeight 26 | 27 | VStack { 28 | VStack { 29 | HomeTabSubViewPostDetailsView(currentUserID: self.profileViewModel.profile!.id, post: post) 30 | .environmentObject(homeViewModel).environmentObject(profileViewModel).environmentObject(medalsViewModel) 31 | } 32 | 33 | VStack { 34 | HStack { 35 | if post.reactionsUsersIDs != nil { 36 | if post.reactionsUsersIDs!.count != 0 { 37 | Image(systemName: post.reactionsUsersIDs!.contains(self.profileViewModel.profile!.id) ? "hand.thumbsup.fill" : "hand.thumbsup") 38 | .foregroundColor(.accentColor) 39 | .padding(.leading, screenWidth * 0.05) 40 | 41 | Text("\(post.reactionsUsersIDs!.count)") 42 | .foregroundColor(Color(uiColor: .systemGray2)) 43 | } 44 | 45 | } 46 | 47 | Spacer() 48 | 49 | if let postComments = homeViewModel.postsComments[post.id] { 50 | Text("\(postComments.count) \(String(localized: "HomeView_post_comments_number"))") 51 | .foregroundColor(Color(uiColor: .systemGray2)) 52 | .padding(.trailing, screenWidth * 0.05) 53 | } 54 | } 55 | } 56 | 57 | VStack { 58 | HStack(spacing: 0) { 59 | Spacer() 60 | 61 | if let reactionsUsersIDs = profileViewModel.profile!.reactedPostsIDs { 62 | Button(action: { 63 | withAnimation { 64 | if reactionsUsersIDs.contains(post.id) { 65 | self.homeViewModel.removeReactionFromPost(postID: post.id) { success in } 66 | } else { 67 | self.homeViewModel.reactToPost(postID: post.id) { success in 68 | self.medalsViewModel.giveUserMedal(medalName: "medalFirstLike") 69 | } 70 | } 71 | } 72 | }, label: { 73 | HStack { 74 | Image(systemName: "hand.thumbsup") 75 | .symbolVariant(reactionsUsersIDs.contains(post.id) ? .fill : .none) 76 | Text(String(localized: "HomeView_like_button")) 77 | } 78 | .foregroundColor(reactionsUsersIDs.contains(post.id) ? .green : (colorScheme == .dark ? .white : .black)) 79 | }) 80 | } else { 81 | Button(action: { 82 | withAnimation { 83 | self.homeViewModel.reactToPost(postID: post.id) { success in 84 | self.medalsViewModel.giveUserMedal(medalName: "medalFirstLike") 85 | } 86 | } 87 | }, label: { 88 | HStack { 89 | Image(systemName: "hand.thumbsup") 90 | Text(String(localized: "HomeView_like_button")) 91 | } 92 | .foregroundColor(colorScheme == .dark ? .white : .black) 93 | }) 94 | } 95 | 96 | Spacer() 97 | 98 | Divider() 99 | 100 | Spacer() 101 | 102 | NavigationLink(destination: PostCommentsView(post: post).environmentObject(homeViewModel).environmentObject(profileViewModel).environmentObject(medalsViewModel).ignoresSafeArea(.keyboard)) { 103 | HStack { 104 | Image(systemName: "bubble.left") 105 | Text(String(localized: "HomeView_comment_button")) 106 | } 107 | .foregroundColor(colorScheme == .dark ? .white : .black) 108 | } 109 | NavigationLink(destination: EmptyView()) { 110 | EmptyView() 111 | } 112 | 113 | Spacer() 114 | } 115 | } 116 | .padding(.vertical, screenHeight * 0.015) 117 | .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 10)) 118 | } 119 | } 120 | } 121 | 122 | struct HomeTabSubViewPostsView_Previews: PreviewProvider { 123 | static var previews: some View { 124 | let homeViewModel = HomeViewModel(forPreviews: true) 125 | let profileViewModel = ProfileViewModel(forPreviews: true) 126 | 127 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 128 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 129 | HomeTabSubViewPostsView(post: homeViewModel.posts![0]) 130 | .environmentObject(homeViewModel) 131 | .environmentObject(profileViewModel) 132 | .preferredColorScheme(colorScheme) 133 | .previewDevice(PreviewDevice(rawValue: deviceName)) 134 | .previewDisplayName(deviceName) 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Fit Vein/Views/HomeTabViews/PostsViews/HomeTabSubViewShareView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeTabSubViewShareView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 08/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeTabSubViewShareView: View { 11 | @EnvironmentObject private var homeViewModel: HomeViewModel 12 | @EnvironmentObject private var profileViewModel: ProfileViewModel 13 | @EnvironmentObject private var medalsViewModel: MedalsViewModel 14 | 15 | @State private var showAddPostSheet = false 16 | 17 | var body: some View { 18 | let screenWidth = UIScreen.screenWidth 19 | let screenHeight = UIScreen.screenHeight 20 | 21 | VStack { 22 | HStack { 23 | Group { 24 | if let profilePicturePhoto = profileViewModel.profilePicturePhoto { 25 | Image(uiImage: profilePicturePhoto) 26 | .resizable() 27 | } else { 28 | Image(uiImage: UIImage(named: "blank-profile-hi")!) 29 | .resizable() 30 | } 31 | } 32 | .aspectRatio(contentMode: .fill) 33 | .clipShape(RoundedRectangle(cornerRadius: 50)) 34 | .frame(width: screenWidth * 0.05, height: screenHeight * 0.05) 35 | .padding(.trailing, screenWidth * 0.04) 36 | 37 | Text(String(localized: "HomeView_share")) 38 | .foregroundColor(Color(uiColor: .systemGray)) 39 | .font(.system(size: screenHeight * 0.02)) 40 | 41 | Spacer() 42 | } 43 | .padding() 44 | .padding(.leading) 45 | .onTapGesture { 46 | withAnimation { 47 | showAddPostSheet.toggle() 48 | } 49 | } 50 | 51 | Divider() 52 | 53 | Text(String(localized: "HomeView_friends_activity")) 54 | .foregroundColor(.accentColor) 55 | .font(.system(size: screenHeight * 0.03, weight: .bold)) 56 | .padding() 57 | .background(Rectangle().foregroundColor(Color(uiColor: .systemGray6)).frame(width: screenWidth)) 58 | 59 | } 60 | .sheet(isPresented: $showAddPostSheet) { 61 | AddPostView().environmentObject(homeViewModel).environmentObject(profileViewModel).environmentObject(medalsViewModel).ignoresSafeArea(.keyboard) 62 | } 63 | } 64 | } 65 | 66 | struct HomeTabSubViewShareView_Previews: PreviewProvider { 67 | static var previews: some View { 68 | let homeViewModel = HomeViewModel(forPreviews: true) 69 | let profileViewModel = ProfileViewModel(forPreviews: true) 70 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 71 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 72 | HomeTabSubViewShareView() 73 | .environmentObject(homeViewModel) 74 | .environmentObject(profileViewModel) 75 | .previewDevice(PreviewDevice(rawValue: deviceName)) 76 | .previewDisplayName(deviceName) 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Fit Vein/Views/ProfileTabViews/WorkoutTabView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutTabView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 25/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WorkoutTabView: View { 11 | @EnvironmentObject private var profileViewModel: ProfileViewModel 12 | @State private var howToDisplay = 0 13 | 14 | var body: some View { 15 | GeometryReader { geometry in 16 | 17 | NavigationView { 18 | Group { 19 | if howToDisplay == 0 { 20 | WorkoutTabViewWindows() 21 | } else { 22 | WorkoutTabViewList() 23 | } 24 | } 25 | .toolbar { 26 | ToolbarItem(placement: .navigationBarTrailing) { 27 | Picker("", selection: $howToDisplay) { 28 | Image(systemName: "squareshape.split.2x2").tag(0) 29 | Image(systemName: "list.bullet").tag(1) 30 | } 31 | .pickerStyle(SegmentedPickerStyle()) 32 | } 33 | } 34 | } 35 | .navigationViewStyle(.stack) 36 | } 37 | } 38 | } 39 | 40 | struct WorkoutTabViewWindows: View { 41 | @EnvironmentObject private var profileViewModel: ProfileViewModel 42 | 43 | var body: some View { 44 | GeometryReader { geometry in 45 | let screenWidth = geometry.size.width 46 | let screenHeight = geometry.size.height 47 | 48 | TabView { 49 | ForEach(profileViewModel.workouts!) { workout in 50 | SingleWorkoutWindowView(workout: workout) 51 | .frame(width: screenWidth, height: screenHeight) 52 | } 53 | } 54 | .tabViewStyle(.page) 55 | .navigationTitle(String(localized: "WorkoutTabView_windows_navigation_title")) 56 | .navigationBarHidden(false) 57 | } 58 | } 59 | } 60 | 61 | struct WorkoutTabViewList: View { 62 | @EnvironmentObject var profileViewModel: ProfileViewModel 63 | 64 | var body: some View { 65 | GeometryReader { geometry in 66 | let screenWidth = geometry.size.width 67 | let screenHeight = geometry.size.height 68 | 69 | List(profileViewModel.workouts!) { workout in 70 | NavigationLink(destination: SingleWorkoutWindowView(workout: workout) 71 | .navigationTitle(String(localized: "WorkoutTabView_list_single_workout_navigation_title")) 72 | .navigationBarHidden(false) 73 | .frame(width: screenWidth, height: screenHeight)) 74 | { 75 | HStack { 76 | Image(uiImage: UIImage(named: "sprint2")!) 77 | .resizable() 78 | .scaledToFit() 79 | .frame(width: screenWidth * 0.1, height: screenHeight * 0.1) 80 | .padding(.trailing) 81 | 82 | VStack { 83 | Text(String(localized: "WorkoutTabView_list_workout_type_name")) 84 | .font(.title3) 85 | .fontWeight(.bold) 86 | Text(getShortDate(longDate: workout.date)) 87 | } 88 | } 89 | } 90 | } 91 | .navigationTitle(String(localized: "WorkoutTabView_list_navigation_title")) 92 | .navigationBarHidden(false) 93 | } 94 | } 95 | } 96 | 97 | struct WorkoutTabView_Previews: PreviewProvider { 98 | static var previews: some View { 99 | let profileViewModel = ProfileViewModel(forPreviews: true) 100 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 101 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 102 | 103 | WorkoutTabView() 104 | .environmentObject(profileViewModel) 105 | .preferredColorScheme(colorScheme) 106 | .previewDevice(PreviewDevice(rawValue: deviceName)) 107 | .previewDisplayName(deviceName) 108 | 109 | WorkoutTabViewWindows() 110 | .environmentObject(profileViewModel) 111 | .preferredColorScheme(colorScheme) 112 | .previewDevice(PreviewDevice(rawValue: deviceName)) 113 | .previewDisplayName(deviceName) 114 | 115 | WorkoutTabViewList() 116 | .environmentObject(profileViewModel) 117 | .preferredColorScheme(colorScheme) 118 | .previewDevice(PreviewDevice(rawValue: deviceName)) 119 | .previewDisplayName(deviceName) 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Fit Vein/Views/WorkoutTabViews/FinishedWorkoutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinishedWorkoutView.swift 3 | // Fit Vein 4 | // 5 | // Created by Łukasz Janiszewski on 26/10/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FinishedWorkoutView: View { 11 | @EnvironmentObject private var workoutViewModel: WorkoutViewModel 12 | @EnvironmentObject private var medalsViewModel: MedalsViewModel 13 | @EnvironmentObject private var networkManager: NetworkManager 14 | @Environment(\.dismiss) var dismiss 15 | @State private var backToBeginning = false 16 | 17 | @State private var error = false 18 | 19 | var body: some View { 20 | GeometryReader { geometry in 21 | let screenWidth = geometry.size.width 22 | let screenHeight = geometry.size.height 23 | 24 | if backToBeginning || workoutViewModel.workout == nil { 25 | withAnimation(.linear) { 26 | WorkoutView() 27 | .environmentObject(workoutViewModel) 28 | .environmentObject(medalsViewModel) 29 | .environmentObject(networkManager) 30 | } 31 | } else { 32 | VStack(spacing: screenHeight * 0.05) { 33 | SingleWorkoutWindowView(workout: workoutViewModel.workout!) 34 | .frame(height: screenHeight * 0.75) 35 | 36 | if error { 37 | HStack { 38 | LottieView(name: "wrongData", loopMode: .loop, contentMode: .scaleAspectFill) 39 | .frame(width: screenWidth * 0.15, height: screenHeight * 0.05) 40 | .padding(.leading) 41 | .offset(y: -screenHeight * 0.013) 42 | Text(String(localized: "FinisehdWorkoutView_saving_error")) 43 | .foregroundColor(.red) 44 | .font(.system(size: screenWidth * 0.035, weight: .bold)) 45 | Spacer() 46 | } 47 | .padding(.horizontal) 48 | .offset(y: -screenHeight * 0.05) 49 | } 50 | 51 | HStack { 52 | Spacer() 53 | 54 | Button(action: { 55 | error = false 56 | self.workoutViewModel.saveWorkoutToDatabase() { success in 57 | withAnimation { 58 | if success { 59 | medalsViewModel.giveUserMedal(medalName: "medalFirstWorkout") 60 | backToBeginning = true 61 | } else { 62 | error = true 63 | } 64 | } 65 | } 66 | }, label: { 67 | Text(String(localized: "FinishedWorkoutView_save_button")) 68 | .fontWeight(.bold) 69 | .foregroundColor(Color(uiColor: .systemGray5)) 70 | }) 71 | .background(RoundedRectangle(cornerRadius: 25).frame(width: screenWidth * 0.35, height: screenHeight * 0.07).foregroundColor(.accentColor)) 72 | .padding() 73 | .disabled(!networkManager.isConnected) 74 | 75 | Spacer() 76 | 77 | Button(action: { 78 | withAnimation(.linear) { 79 | backToBeginning = true 80 | } 81 | }, label: { 82 | Text(String(localized: "FinishedWorkoutView_discard_button")) 83 | .foregroundColor(Color(uiColor: .systemGray5)) 84 | }) 85 | .background(RoundedRectangle(cornerRadius: 25).frame(width: screenWidth * 0.35, height: screenHeight * 0.07).foregroundColor(.red)) 86 | .padding() 87 | 88 | Spacer() 89 | } 90 | .padding(.bottom, screenHeight * 0.05) 91 | } 92 | .onDisappear { 93 | dismiss() 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | struct FinishedWorkoutView_Previews: PreviewProvider { 101 | static var previews: some View { 102 | let sessionStore = SessionStore(forPreviews: true) 103 | let workoutViewModel = WorkoutViewModel(forPreviews: true) 104 | let networkManager = NetworkManager() 105 | ForEach(ColorScheme.allCases, id: \.self) { colorScheme in 106 | ForEach(["iPhone XS MAX", "iPhone 8"], id: \.self) { deviceName in 107 | FinishedWorkoutView() 108 | .preferredColorScheme(colorScheme) 109 | .previewDevice(PreviewDevice(rawValue: deviceName)) 110 | .previewDisplayName(deviceName) 111 | .environmentObject(sessionStore) 112 | .environmentObject(workoutViewModel) 113 | .environmentObject(networkManager) 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fit-Vein 2 | 3 | ## Table of Content 4 | * [General Info](#general-info) 5 | * [Technologies](#technologies) 6 | * [Status](#status) 7 | * [Requirements](#requirements) 8 | * [Functionalities](#functionalities) 9 | * [Screenshots](#screenshots) 10 | * [Preview Video](#preview) 11 | 12 | 13 | ## General info 14 | iOS fitness app in which user can perform various interval workouts, share their activity with people followed by creating posts, adding reactions and commenting others activity. What's more, it is possible to monitor health data delivered from the HealthKit and see already completed workouts. 15 | 16 | 17 | ## Technologies 18 | Swift 5.5 [SwiftUI + UIKit] 19 | Xcode 13 20 | Firebase, Firebase Firestore, Firebase Storage 21 | HealthKit 22 | Lottie Library 23 | 24 | 25 | ## Status 26 | Finished 27 | 28 | 29 | ## Requirements 30 | Apple iPhone with iOS 15+ installed 31 | 32 | 33 | ## Functionalities 34 | * Home Tab in which users can share recent workouts by creating posts, add photos to them, write whatever they want and react by liking and commenting posts of other users 35 | * The possibility of performing interval training during which they have time, series, calories etc. counter 36 | * Users can make use of use pre-created workouts or create new, custom ones 37 | * Up-to-date HealthKit data in the Profile tab where users can see their daily burned calories, distance traveled and average heartRate measured by iPhone or Apple Watch 38 | * Achieving new levels along with increasing number of completed workouts, new levels bring new medals 39 | * The possibility of changing user's data including e-mail address, password, signing out and deleting user's account 40 | * Biometric security (FaceID and TouchID) 41 | * Special offline mode where all network functions are turned off. Still it is possible to do workouts and see HealthKit data 42 | * During workout we are allowed to minimize the app or lock the screen and workouts progress will still be calculated in background and displayed properly updated after coming back to app 43 | * Full English and Polish localization 44 | * Light and dark color themes 45 | * Full onboarding, executing at first app's launch, taking the user on a tour across all of the app's most vital functionalities. 46 | 47 | 48 | ## Screenshots 49 | 50 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Images/FitVeinIconDark.png?raw=true) 51 | 52 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/HomeTabView.png?raw=true) 53 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/PostCommentsView.png?raw=true) 54 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/SearchFriendsView.png?raw=true) 55 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/WorkoutsView.png?raw=true) 56 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/WorkoutTimerView.png?raw=true) 57 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/FinishedWorkoutView.png?raw=true) 58 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/ProfileView.png?raw=true) 59 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/HealthTabView.png?raw=true) 60 | ![alt text](https://github.com/Vader20FF/Fit-Vein/blob/main/Fit%20Vein/Onboarding%20Photos/WorkoutsTabView.png?raw=true) 61 | 62 | 63 | ## Preview Video 64 | ### Polish UI Localization 65 | https://user-images.githubusercontent.com/78795431/173231961-d1ca756b-2498-467a-8709-29c1f9e4cfd6.mov 66 | --------------------------------------------------------------------------------