├── 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 | 
51 |
52 | 
53 | 
54 | 
55 | 
56 | 
57 | 
58 | 
59 | 
60 | 
61 |
62 |
63 | ## Preview Video
64 | ### Polish UI Localization
65 | https://user-images.githubusercontent.com/78795431/173231961-d1ca756b-2498-467a-8709-29c1f9e4cfd6.mov
66 |
--------------------------------------------------------------------------------