diff --git a/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj b/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj index efc207c09a8bd1d45555f7071db8f37ae247f169..75ddafcb518980e4d724651ad89521f47253da04 100644 --- a/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj +++ b/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj @@ -7,9 +7,53 @@ objects = { /* Begin PBXBuildFile section */ + 2723CF5C27FC6E4500210416 /* TransfereScaffolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF5B27FC6E4500210416 /* TransfereScaffolding.swift */; }; + 2723CF8527FECE6E00210416 /* CacheEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF8427FECE6E00210416 /* CacheEntry.swift */; }; + 2723CF8827FEF72C00210416 /* CacheImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF8727FEF72C00210416 /* CacheImplementation.swift */; }; + 2723CF8A27FEFA1400210416 /* ProjectData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF8927FEFA1400210416 /* ProjectData.swift */; }; + 2736C04D27FAD2E90038BD7B /* ProjectInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2736C04C27FAD2E90038BD7B /* ProjectInfoView.swift */; }; + 27373E7B280EAF0100C83150 /* FilterProjectArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27373E7A280EAF0100C83150 /* FilterProjectArea.swift */; }; + 27373E7D280F10F100C83150 /* FilterProjectSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27373E7C280F10F100C83150 /* FilterProjectSize.swift */; }; + 27373E7F281023D900C83150 /* FilterProjectPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27373E7E281023D900C83150 /* FilterProjectPeriod.swift */; }; 274C541127EE02F2002CE76A /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541027EE02F2002CE76A /* BlurView.swift */; }; - 274C541327EE0316002CE76A /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541227EE0316002CE76A /* MapView.swift */; }; - 274C541527EE0339002CE76A /* CustomCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541427EE0339002CE76A /* CustomCorners.swift */; }; + 274C541827F1B0A5002CE76A /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541727F1B0A5002CE76A /* MapView.swift */; }; + 274C541A27F1B277002CE76A /* NavigationBarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541927F1B277002CE76A /* NavigationBarBottom.swift */; }; + 274C541D27F1B44C002CE76A /* ScaffoldingTransfere.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541C27F1B44C002CE76A /* ScaffoldingTransfere.swift */; }; + 274C542427F1D0DC002CE76A /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542327F1D0DC002CE76A /* ProfileView.swift */; }; + 274C542627F1D133002CE76A /* ProjectListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542527F1D133002CE76A /* ProjectListView.swift */; }; + 274C542827F1DEC3002CE76A /* CircleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542727F1DEC3002CE76A /* CircleImage.swift */; }; + 274C542E27F1F3CD002CE76A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542D27F1F3CD002CE76A /* User.swift */; }; + 274C543227F311E6002CE76A /* MapDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C543127F311E6002CE76A /* MapDisplay.swift */; }; + 274C543427F34978002CE76A /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C543327F34977002CE76A /* MapViewModel.swift */; }; + 274C543927F4889C002CE76A /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C543827F4889C002CE76A /* Project.swift */; }; + 27D2D6CA282AF0DD0041D014 /* ProfileData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D2D6C9282AF0DC0041D014 /* ProfileData.swift */; }; + 27D2D6CC282AF1040041D014 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D2D6CB282AF1040041D014 /* Profile.swift */; }; + 27E980A5281710CC00118DB3 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980A4281710CC00118DB3 /* FilterView.swift */; }; + 27E980AB2819D4C100118DB3 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980AA2819D4C100118DB3 /* CalendarView.swift */; }; + 27E980AD2819D8DE00118DB3 /* CheckBoxRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980AC2819D8DE00118DB3 /* CheckBoxRow.swift */; }; + 27E980B0281B085100118DB3 /* ActiveSizeFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980AF281B085100118DB3 /* ActiveSizeFilterView.swift */; }; + 27E980B3281B089300118DB3 /* ActiveAreaFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980B2281B089300118DB3 /* ActiveAreaFilterView.swift */; }; + 27E980B5281B08C000118DB3 /* ActivePeriodFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980B4281B08C000118DB3 /* ActivePeriodFilterView.swift */; }; + 27E980B7281B0A1A00118DB3 /* FilterProjectStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980B6281B0A1A00118DB3 /* FilterProjectStatus.swift */; }; + 27E980BB281B26B900118DB3 /* ActiveStatusFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980BA281B26B900118DB3 /* ActiveStatusFilterView.swift */; }; + 27E980BD281B2A6C00118DB3 /* SizeBetweenFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980BC281B2A6C00118DB3 /* SizeBetweenFilter.swift */; }; + 27E980BF281B383200118DB3 /* SizeGreaterThanFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980BE281B383200118DB3 /* SizeGreaterThanFilter.swift */; }; + 27E980C1281B384600118DB3 /* SizeLessThanFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C0281B384600118DB3 /* SizeLessThanFilter.swift */; }; + 27E980C4281BE49200118DB3 /* CornerRadiusStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C3281BE49200118DB3 /* CornerRadiusStyle.swift */; }; + 27E980C8281BFA6D00118DB3 /* ScaffoldingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C7281BFA6D00118DB3 /* ScaffoldingView.swift */; }; + 27E980CA281BFAA700118DB3 /* ProjectInfoDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C9281BFAA700118DB3 /* ProjectInfoDetailedView.swift */; }; + 27E980CD281E9F5300118DB3 /* TransfereScaffoldingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980CC281E9F5300118DB3 /* TransfereScaffoldingButton.swift */; }; + 27E980CF281E9F9800118DB3 /* ScaffoldingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980CE281E9F9800118DB3 /* ScaffoldingItems.swift */; }; + 27E980D1281E9FBB00118DB3 /* ScaffoldingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980D0281E9FBB00118DB3 /* ScaffoldingItem.swift */; }; + 27E980D42822EC8100118DB3 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 27E980D32822EC8100118DB3 /* FirebaseAuth */; }; + 27E980D82822F50B00118DB3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27E980D72822F50A00118DB3 /* GoogleService-Info.plist */; }; + 27E980DD282409CC00118DB3 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980DC282409CC00118DB3 /* SignInView.swift */; }; + 27E980DF28240A1E00118DB3 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980DE28240A1E00118DB3 /* SignUpView.swift */; }; + 27E980E128240A5200118DB3 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E028240A5200118DB3 /* AppViewModel.swift */; }; + 27E980E42829AE5E00118DB3 /* TransfereScaffoldingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E32829AE5E00118DB3 /* TransfereScaffoldingView.swift */; }; + 27E980E62829B0E600118DB3 /* HistoryOfScaffolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E52829B0E600118DB3 /* HistoryOfScaffolding.swift */; }; + 27E980E82829B3A700118DB3 /* ScaffoldingDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E72829B3A700118DB3 /* ScaffoldingDetailedView.swift */; }; + 27E980EB2829B5F200118DB3 /* TextFieldModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980EA2829B5F200118DB3 /* TextFieldModifiers.swift */; }; 4D17100627D755400026C216 /* stillasMobileApplicationApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D17100527D755400026C216 /* stillasMobileApplicationApp.swift */; }; 4D17100827D755400026C216 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D17100727D755400026C216 /* ContentView.swift */; }; 4D17100A27D755410026C216 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4D17100927D755410026C216 /* Assets.xcassets */; }; @@ -17,9 +61,53 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2723CF5B27FC6E4500210416 /* TransfereScaffolding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfereScaffolding.swift; sourceTree = "<group>"; }; + 2723CF8427FECE6E00210416 /* CacheEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheEntry.swift; sourceTree = "<group>"; }; + 2723CF8727FEF72C00210416 /* CacheImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheImplementation.swift; sourceTree = "<group>"; }; + 2723CF8927FEFA1400210416 /* ProjectData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectData.swift; sourceTree = "<group>"; }; + 2736C04C27FAD2E90038BD7B /* ProjectInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectInfoView.swift; sourceTree = "<group>"; }; + 27373E7A280EAF0100C83150 /* FilterProjectArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectArea.swift; sourceTree = "<group>"; }; + 27373E7C280F10F100C83150 /* FilterProjectSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectSize.swift; sourceTree = "<group>"; }; + 27373E7E281023D900C83150 /* FilterProjectPeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectPeriod.swift; sourceTree = "<group>"; }; 274C541027EE02F2002CE76A /* BlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = "<group>"; }; - 274C541227EE0316002CE76A /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; }; - 274C541427EE0339002CE76A /* CustomCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCorners.swift; sourceTree = "<group>"; }; + 274C541727F1B0A5002CE76A /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; }; + 274C541927F1B277002CE76A /* NavigationBarBottom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarBottom.swift; sourceTree = "<group>"; }; + 274C541C27F1B44C002CE76A /* ScaffoldingTransfere.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingTransfere.swift; sourceTree = "<group>"; }; + 274C542327F1D0DC002CE76A /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; }; + 274C542527F1D133002CE76A /* ProjectListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectListView.swift; sourceTree = "<group>"; }; + 274C542727F1DEC3002CE76A /* CircleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImage.swift; sourceTree = "<group>"; }; + 274C542D27F1F3CD002CE76A /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; }; + 274C543127F311E6002CE76A /* MapDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDisplay.swift; sourceTree = "<group>"; }; + 274C543327F34977002CE76A /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = "<group>"; }; + 274C543527F44104002CE76A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; + 274C543827F4889C002CE76A /* Project.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = "<group>"; }; + 27D2D6C9282AF0DC0041D014 /* ProfileData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileData.swift; sourceTree = "<group>"; }; + 27D2D6CB282AF1040041D014 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; }; + 27E980A4281710CC00118DB3 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = "<group>"; }; + 27E980AA2819D4C100118DB3 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; }; + 27E980AC2819D8DE00118DB3 /* CheckBoxRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxRow.swift; sourceTree = "<group>"; }; + 27E980AF281B085100118DB3 /* ActiveSizeFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSizeFilterView.swift; sourceTree = "<group>"; }; + 27E980B2281B089300118DB3 /* ActiveAreaFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveAreaFilterView.swift; sourceTree = "<group>"; }; + 27E980B4281B08C000118DB3 /* ActivePeriodFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePeriodFilterView.swift; sourceTree = "<group>"; }; + 27E980B6281B0A1A00118DB3 /* FilterProjectStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectStatus.swift; sourceTree = "<group>"; }; + 27E980BA281B26B900118DB3 /* ActiveStatusFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveStatusFilterView.swift; sourceTree = "<group>"; }; + 27E980BC281B2A6C00118DB3 /* SizeBetweenFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeBetweenFilter.swift; sourceTree = "<group>"; }; + 27E980BE281B383200118DB3 /* SizeGreaterThanFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeGreaterThanFilter.swift; sourceTree = "<group>"; }; + 27E980C0281B384600118DB3 /* SizeLessThanFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeLessThanFilter.swift; sourceTree = "<group>"; }; + 27E980C3281BE49200118DB3 /* CornerRadiusStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusStyle.swift; sourceTree = "<group>"; }; + 27E980C7281BFA6D00118DB3 /* ScaffoldingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingView.swift; sourceTree = "<group>"; }; + 27E980C9281BFAA700118DB3 /* ProjectInfoDetailedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectInfoDetailedView.swift; sourceTree = "<group>"; }; + 27E980CC281E9F5300118DB3 /* TransfereScaffoldingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfereScaffoldingButton.swift; sourceTree = "<group>"; }; + 27E980CE281E9F9800118DB3 /* ScaffoldingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingItems.swift; sourceTree = "<group>"; }; + 27E980D0281E9FBB00118DB3 /* ScaffoldingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingItem.swift; sourceTree = "<group>"; }; + 27E980D72822F50A00118DB3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; + 27E980DC282409CC00118DB3 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; }; + 27E980DE28240A1E00118DB3 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = "<group>"; }; + 27E980E028240A5200118DB3 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = "<group>"; }; + 27E980E32829AE5E00118DB3 /* TransfereScaffoldingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfereScaffoldingView.swift; sourceTree = "<group>"; }; + 27E980E52829B0E600118DB3 /* HistoryOfScaffolding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryOfScaffolding.swift; sourceTree = "<group>"; }; + 27E980E72829B3A700118DB3 /* ScaffoldingDetailedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingDetailedView.swift; sourceTree = "<group>"; }; + 27E980EA2829B5F200118DB3 /* TextFieldModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldModifiers.swift; sourceTree = "<group>"; }; 4D17100227D755400026C216 /* stillasMobileApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = stillasMobileApplication.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4D17100527D755400026C216 /* stillasMobileApplicationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = stillasMobileApplicationApp.swift; sourceTree = "<group>"; }; 4D17100727D755400026C216 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; @@ -32,12 +120,43 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 27E980D42822EC8100118DB3 /* FirebaseAuth in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2723CF7E27FD9EA800210416 /* FetchingApiData */ = { + isa = PBXGroup; + children = ( + 2723CF8927FEFA1400210416 /* ProjectData.swift */, + 27D2D6C9282AF0DC0041D014 /* ProfileData.swift */, + ); + path = FetchingApiData; + sourceTree = "<group>"; + }; + 2723CF8627FEF60C00210416 /* Cache */ = { + isa = PBXGroup; + children = ( + 2723CF8427FECE6E00210416 /* CacheEntry.swift */, + 2723CF8727FEF72C00210416 /* CacheImplementation.swift */, + ); + path = Cache; + sourceTree = "<group>"; + }; + 27373E79280EAEBF00C83150 /* FilterData */ = { + isa = PBXGroup; + children = ( + 27E980AE281B083800118DB3 /* FilterView */, + 27E980A928193A8500118DB3 /* StatusFilter */, + 27E980A828193A7D00118DB3 /* PeriodFilter */, + 27E980A728193A7300118DB3 /* SizeFilter */, + 27E980A628193A6200118DB3 /* AreaFilter */, + ); + path = FilterData; + sourceTree = "<group>"; + }; 274C540E27EE02C4002CE76A /* BlurView */ = { isa = PBXGroup; children = ( @@ -46,24 +165,211 @@ path = BlurView; sourceTree = "<group>"; }; - 274C540F27EE02DD002CE76A /* MapView */ = { + 274C541627F1B097002CE76A /* MapView */ = { isa = PBXGroup; children = ( - 274C541227EE0316002CE76A /* MapView.swift */, + 274C541727F1B0A5002CE76A /* MapView.swift */, + 274C543127F311E6002CE76A /* MapDisplay.swift */, + 274C543327F34977002CE76A /* MapViewModel.swift */, ); path = MapView; sourceTree = "<group>"; }; + 274C541E27F1B4BB002CE76A /* Model */ = { + isa = PBXGroup; + children = ( + 2723CF7E27FD9EA800210416 /* FetchingApiData */, + 274C541C27F1B44C002CE76A /* ScaffoldingTransfere.swift */, + 274C542D27F1F3CD002CE76A /* User.swift */, + 274C543827F4889C002CE76A /* Project.swift */, + 27D2D6CB282AF1040041D014 /* Profile.swift */, + ); + path = Model; + sourceTree = "<group>"; + }; + 274C542927F1DEFC002CE76A /* Helpers */ = { + isa = PBXGroup; + children = ( + 274C542727F1DEC3002CE76A /* CircleImage.swift */, + 27E980AC2819D8DE00118DB3 /* CheckBoxRow.swift */, + 27E980C3281BE49200118DB3 /* CornerRadiusStyle.swift */, + 27E980EA2829B5F200118DB3 /* TextFieldModifiers.swift */, + ); + path = Helpers; + sourceTree = "<group>"; + }; + 274C542F27F2E03A002CE76A /* ProfileView */ = { + isa = PBXGroup; + children = ( + 274C542327F1D0DC002CE76A /* ProfileView.swift */, + ); + path = ProfileView; + sourceTree = "<group>"; + }; + 274C543027F2E049002CE76A /* ProjectView */ = { + isa = PBXGroup; + children = ( + 27373E79280EAEBF00C83150 /* FilterData */, + 27E980C5281BF9E100118DB3 /* ProjectView */, + ); + path = ProjectView; + sourceTree = "<group>"; + }; 27E5158527EE00D900EFE9A1 /* Views */ = { isa = PBXGroup; children = ( - 274C540F27EE02DD002CE76A /* MapView */, + 2723CF8627FEF60C00210416 /* Cache */, + 274C543027F2E049002CE76A /* ProjectView */, + 274C542F27F2E03A002CE76A /* ProfileView */, + 274C541E27F1B4BB002CE76A /* Model */, + 274C541627F1B097002CE76A /* MapView */, 274C540E27EE02C4002CE76A /* BlurView */, - 274C541427EE0339002CE76A /* CustomCorners.swift */, + 274C541927F1B277002CE76A /* NavigationBarBottom.swift */, + 274C542927F1DEFC002CE76A /* Helpers */, ); path = Views; sourceTree = "<group>"; }; + 27E980A628193A6200118DB3 /* AreaFilter */ = { + isa = PBXGroup; + children = ( + 27373E7A280EAF0100C83150 /* FilterProjectArea.swift */, + ); + path = AreaFilter; + sourceTree = "<group>"; + }; + 27E980A728193A7300118DB3 /* SizeFilter */ = { + isa = PBXGroup; + children = ( + 27E980C2281B386200118DB3 /* BetweenGreaterLess */, + 27373E7C280F10F100C83150 /* FilterProjectSize.swift */, + ); + path = SizeFilter; + sourceTree = "<group>"; + }; + 27E980A828193A7D00118DB3 /* PeriodFilter */ = { + isa = PBXGroup; + children = ( + 27373E7E281023D900C83150 /* FilterProjectPeriod.swift */, + 27E980AA2819D4C100118DB3 /* CalendarView.swift */, + ); + path = PeriodFilter; + sourceTree = "<group>"; + }; + 27E980A928193A8500118DB3 /* StatusFilter */ = { + isa = PBXGroup; + children = ( + 27E980B6281B0A1A00118DB3 /* FilterProjectStatus.swift */, + ); + path = StatusFilter; + sourceTree = "<group>"; + }; + 27E980AE281B083800118DB3 /* FilterView */ = { + isa = PBXGroup; + children = ( + 27E980B1281B087100118DB3 /* ActiveFilterViews */, + 27E980A4281710CC00118DB3 /* FilterView.swift */, + ); + path = FilterView; + sourceTree = "<group>"; + }; + 27E980B1281B087100118DB3 /* ActiveFilterViews */ = { + isa = PBXGroup; + children = ( + 27E980AF281B085100118DB3 /* ActiveSizeFilterView.swift */, + 27E980B2281B089300118DB3 /* ActiveAreaFilterView.swift */, + 27E980B4281B08C000118DB3 /* ActivePeriodFilterView.swift */, + 27E980BA281B26B900118DB3 /* ActiveStatusFilterView.swift */, + ); + path = ActiveFilterViews; + sourceTree = "<group>"; + }; + 27E980C2281B386200118DB3 /* BetweenGreaterLess */ = { + isa = PBXGroup; + children = ( + 27E980BC281B2A6C00118DB3 /* SizeBetweenFilter.swift */, + 27E980BE281B383200118DB3 /* SizeGreaterThanFilter.swift */, + 27E980C0281B384600118DB3 /* SizeLessThanFilter.swift */, + ); + path = BetweenGreaterLess; + sourceTree = "<group>"; + }; + 27E980C5281BF9E100118DB3 /* ProjectView */ = { + isa = PBXGroup; + children = ( + 274C542527F1D133002CE76A /* ProjectListView.swift */, + 27E980C6281BF9FE00118DB3 /* ProjectDetailView */, + ); + path = ProjectView; + sourceTree = "<group>"; + }; + 27E980C6281BF9FE00118DB3 /* ProjectDetailView */ = { + isa = PBXGroup; + children = ( + 27E980CB281E9F3900118DB3 /* ScaffoldingView */, + 2736C04C27FAD2E90038BD7B /* ProjectInfoView.swift */, + 27E980C9281BFAA700118DB3 /* ProjectInfoDetailedView.swift */, + ); + path = ProjectDetailView; + sourceTree = "<group>"; + }; + 27E980CB281E9F3900118DB3 /* ScaffoldingView */ = { + isa = PBXGroup; + children = ( + 27E980C7281BFA6D00118DB3 /* ScaffoldingView.swift */, + 27E980CE281E9F9800118DB3 /* ScaffoldingItems.swift */, + 27E980D0281E9FBB00118DB3 /* ScaffoldingItem.swift */, + 27E980E92829B49B00118DB3 /* ScaffoldingHistory */, + 27E980E22829A6E600118DB3 /* TransfereScaffolding */, + ); + name = ScaffoldingView; + sourceTree = "<group>"; + }; + 27E980D92824099100118DB3 /* Login&Signup */ = { + isa = PBXGroup; + children = ( + 27E980DB282409BB00118DB3 /* SignUp */, + 27E980DA282409B600118DB3 /* Login */, + 27E980E028240A5200118DB3 /* AppViewModel.swift */, + ); + path = "Login&Signup"; + sourceTree = "<group>"; + }; + 27E980DA282409B600118DB3 /* Login */ = { + isa = PBXGroup; + children = ( + 27E980DC282409CC00118DB3 /* SignInView.swift */, + ); + path = Login; + sourceTree = "<group>"; + }; + 27E980DB282409BB00118DB3 /* SignUp */ = { + isa = PBXGroup; + children = ( + 27E980DE28240A1E00118DB3 /* SignUpView.swift */, + ); + path = SignUp; + sourceTree = "<group>"; + }; + 27E980E22829A6E600118DB3 /* TransfereScaffolding */ = { + isa = PBXGroup; + children = ( + 2723CF5B27FC6E4500210416 /* TransfereScaffolding.swift */, + 27E980E32829AE5E00118DB3 /* TransfereScaffoldingView.swift */, + ); + name = TransfereScaffolding; + sourceTree = "<group>"; + }; + 27E980E92829B49B00118DB3 /* ScaffoldingHistory */ = { + isa = PBXGroup; + children = ( + 27E980E72829B3A700118DB3 /* ScaffoldingDetailedView.swift */, + 27E980CC281E9F5300118DB3 /* TransfereScaffoldingButton.swift */, + 27E980E52829B0E600118DB3 /* HistoryOfScaffolding.swift */, + ); + name = ScaffoldingHistory; + sourceTree = "<group>"; + }; 4D170FF927D755400026C216 = { isa = PBXGroup; children = ( @@ -83,7 +389,10 @@ 4D17100427D755400026C216 /* stillasMobileApplication */ = { isa = PBXGroup; children = ( + 27E980D72822F50A00118DB3 /* GoogleService-Info.plist */, + 274C543527F44104002CE76A /* Info.plist */, 27E5158527EE00D900EFE9A1 /* Views */, + 27E980D92824099100118DB3 /* Login&Signup */, 4D17100527D755400026C216 /* stillasMobileApplicationApp.swift */, 4D17100727D755400026C216 /* ContentView.swift */, 4D17100927D755410026C216 /* Assets.xcassets */, @@ -116,6 +425,9 @@ dependencies = ( ); name = stillasMobileApplication; + packageProductDependencies = ( + 27E980D32822EC8100118DB3 /* FirebaseAuth */, + ); productName = stillasMobileApplication; productReference = 4D17100227D755400026C216 /* stillasMobileApplication.app */; productType = "com.apple.product-type.application"; @@ -144,6 +456,9 @@ Base, ); mainGroup = 4D170FF927D755400026C216; + packageReferences = ( + 27E980D22822EC8100118DB3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + ); productRefGroup = 4D17100327D755400026C216 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -159,6 +474,7 @@ buildActionMask = 2147483647; files = ( 4D17100D27D755410026C216 /* Preview Assets.xcassets in Resources */, + 27E980D82822F50B00118DB3 /* GoogleService-Info.plist in Resources */, 4D17100A27D755410026C216 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -170,11 +486,53 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 27E980A5281710CC00118DB3 /* FilterView.swift in Sources */, 4D17100827D755400026C216 /* ContentView.swift in Sources */, - 274C541327EE0316002CE76A /* MapView.swift in Sources */, + 27E980BD281B2A6C00118DB3 /* SizeBetweenFilter.swift in Sources */, + 274C541A27F1B277002CE76A /* NavigationBarBottom.swift in Sources */, + 27373E7B280EAF0100C83150 /* FilterProjectArea.swift in Sources */, + 27E980E82829B3A700118DB3 /* ScaffoldingDetailedView.swift in Sources */, + 2723CF5C27FC6E4500210416 /* TransfereScaffolding.swift in Sources */, + 27E980B7281B0A1A00118DB3 /* FilterProjectStatus.swift in Sources */, + 274C541827F1B0A5002CE76A /* MapView.swift in Sources */, + 27E980B5281B08C000118DB3 /* ActivePeriodFilterView.swift in Sources */, + 27E980E62829B0E600118DB3 /* HistoryOfScaffolding.swift in Sources */, + 27E980E128240A5200118DB3 /* AppViewModel.swift in Sources */, + 27E980DD282409CC00118DB3 /* SignInView.swift in Sources */, + 274C542827F1DEC3002CE76A /* CircleImage.swift in Sources */, + 27E980AB2819D4C100118DB3 /* CalendarView.swift in Sources */, + 27E980E42829AE5E00118DB3 /* TransfereScaffoldingView.swift in Sources */, + 27E980C1281B384600118DB3 /* SizeLessThanFilter.swift in Sources */, + 27373E7F281023D900C83150 /* FilterProjectPeriod.swift in Sources */, + 27E980B3281B089300118DB3 /* ActiveAreaFilterView.swift in Sources */, + 274C543227F311E6002CE76A /* MapDisplay.swift in Sources */, + 274C543427F34978002CE76A /* MapViewModel.swift in Sources */, + 2723CF8A27FEFA1400210416 /* ProjectData.swift in Sources */, + 274C542E27F1F3CD002CE76A /* User.swift in Sources */, + 27E980CA281BFAA700118DB3 /* ProjectInfoDetailedView.swift in Sources */, + 27E980B0281B085100118DB3 /* ActiveSizeFilterView.swift in Sources */, + 27E980D1281E9FBB00118DB3 /* ScaffoldingItem.swift in Sources */, + 27E980CF281E9F9800118DB3 /* ScaffoldingItems.swift in Sources */, + 27D2D6CC282AF1040041D014 /* Profile.swift in Sources */, + 27E980BF281B383200118DB3 /* SizeGreaterThanFilter.swift in Sources */, + 27E980DF28240A1E00118DB3 /* SignUpView.swift in Sources */, + 274C541D27F1B44C002CE76A /* ScaffoldingTransfere.swift in Sources */, + 27E980AD2819D8DE00118DB3 /* CheckBoxRow.swift in Sources */, + 27E980C4281BE49200118DB3 /* CornerRadiusStyle.swift in Sources */, + 274C542627F1D133002CE76A /* ProjectListView.swift in Sources */, + 2723CF8527FECE6E00210416 /* CacheEntry.swift in Sources */, + 2723CF8827FEF72C00210416 /* CacheImplementation.swift in Sources */, + 27E980EB2829B5F200118DB3 /* TextFieldModifiers.swift in Sources */, + 2736C04D27FAD2E90038BD7B /* ProjectInfoView.swift in Sources */, 4D17100627D755400026C216 /* stillasMobileApplicationApp.swift in Sources */, + 27373E7D280F10F100C83150 /* FilterProjectSize.swift in Sources */, 274C541127EE02F2002CE76A /* BlurView.swift in Sources */, - 274C541527EE0339002CE76A /* CustomCorners.swift in Sources */, + 27E980CD281E9F5300118DB3 /* TransfereScaffoldingButton.swift in Sources */, + 27E980C8281BFA6D00118DB3 /* ScaffoldingView.swift in Sources */, + 27D2D6CA282AF0DD0041D014 /* ProfileData.swift in Sources */, + 27E980BB281B26B900118DB3 /* ActiveStatusFilterView.swift in Sources */, + 274C543927F4889C002CE76A /* Project.swift in Sources */, + 274C542427F1D0DC002CE76A /* ProfileView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,7 +590,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -287,7 +645,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -305,20 +663,23 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"stillasMobileApplication/Preview Content\""; - DEVELOPMENT_TEAM = J3YJ2A9XWC; + DEVELOPMENT_TEAM = 9M752RTPAF; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = stillasMobileApplication/Info.plist; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "We use your location to show you where you are located on the map"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.-stillasMobileApplication"; + PRODUCT_BUNDLE_IDENTIFIER = bachelor.stillasMobileApplication; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -334,14 +695,17 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"stillasMobileApplication/Preview Content\""; - DEVELOPMENT_TEAM = J3YJ2A9XWC; + DEVELOPMENT_TEAM = 9M752RTPAF; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = stillasMobileApplication/Info.plist; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "We use your location to show you where you are located on the map"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -377,6 +741,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 27E980D22822EC8100118DB3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 27E980D32822EC8100118DB3 /* FirebaseAuth */ = { + isa = XCSwiftPackageProductDependency; + package = 27E980D22822EC8100118DB3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAuth; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 4D170FFA27D755400026C216 /* Project object */; } diff --git a/stillasMobileApplication/stillasMobileApplication/ContentView.swift b/stillasMobileApplication/stillasMobileApplication/ContentView.swift index 8645d6ea3b086153bbce4d9aabd09129945fb170..b4071db45969dc1e29107d7a3231630f933e47e1 100644 --- a/stillasMobileApplication/stillasMobileApplication/ContentView.swift +++ b/stillasMobileApplication/stillasMobileApplication/ContentView.swift @@ -7,20 +7,21 @@ import SwiftUI +/// **ContentView** +/// Responsible for the views in the application. +/// This will need enum and TabView on a later stage to switch between views. /// https://www.youtube.com/watch?v=vPCEIPL0U_k /// https://firebase.google.com/docs/auth/ios/start -/** - ContentView is responsible for the views in the application. - This will need enum and TabView on a later stage to switch between views. - */ struct ContentView: View { @State var email = "" @State var password = "" + /// The model responsible for sign in and sign up @EnvironmentObject var viewModel: AppViewModel var body: some View { ZStack { + /// If user is signed in, give the user access to the application, if not prompt the user with the login and sign up view if viewModel.signedIn { NavigationBarBottom() } else { @@ -28,6 +29,7 @@ struct ContentView: View { } } .onAppear { + /// Remembers if the user was signed in and closes the application viewModel.signedIn = viewModel.isSignedIn } } diff --git a/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift b/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift index c5706eeb95a1565f1d2835c06a32503213b44a08..635dd004875d9a8ca22907f334318969f097cd80 100644 --- a/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift +++ b/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift @@ -7,24 +7,37 @@ import FirebaseAuth +/// **AppViewModel** +/// The prosessing class responsible for login and sign up through Firebase Authentication +/// Code taken from and inspired from the following sources: /// https://www.youtube.com/watch?v=vPCEIPL0U_k /// https://firebase.google.com/docs/auth/ios/start class AppViewModel: ObservableObject { let auth = Auth.auth() + /// Is user signed in? @Published var signedIn: Bool = false + /// Getter and Setter for the userID var userID: String { get { return auth.currentUser?.uid ?? "" } set { self.userID = newValue } } + /// Getter for is user signed in? var isSignedIn: Bool { return auth.currentUser != nil } + + /// Attempts to sign in user to the application by authorizing the user through the Firebase Authentication + /// - Parameters: + /// - email: The email of the user + /// - password: The password of the user func signIn(email: String, password: String) { + + /// Attempts to authorize the credentials in Firebase Authentication auth.signIn(withEmail: email, password: password) { [weak self ] (result, error) in guard result != nil, error == nil else { return @@ -36,7 +49,13 @@ class AppViewModel: ObservableObject { } } + + /// Attempts to register a new user to the application by adding them to the Firebase Authentication + /// - Parameters: + /// - email: The email of the new user + /// - password: The password of the new user func signUp(email: String, password: String) { + /// Attempts to create a new user auth.createUser(withEmail: email, password: password) { [weak self ] (result, error) in guard result != nil, error == nil else { return @@ -48,6 +67,8 @@ class AppViewModel: ObservableObject { } } + + /// Signs the user out of the application func signOut() { try? auth.signOut() diff --git a/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift b/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift index c6e36efd4a5cdb08e66722dd16491e6c6878822c..a6a9e1e314b85373cf1b8579263c2c12fbc05b57 100644 --- a/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift @@ -8,6 +8,9 @@ import SwiftUI import MapKit +/// **SignInView** +/// The view responsible for the sign in for users +/// Code are inspired from: /// https://medium.com/@success.anil.kk/login-screen-demo-with-swiftui-2f711e0c657d /// https://www.youtube.com/watch?v=vPCEIPL0U_k /// https://firebase.google.com/docs/auth/ios/start @@ -15,102 +18,81 @@ struct SignInView: View { @State var email = "" @State var password = "" + /// The model responsible for sign in @EnvironmentObject var viewModel: AppViewModel let verticalPaddingForForm = 40.0 - var body: some View { - NavigationView{ + + var body: some View { + NavigationView{ + ZStack { + /// Darkened background of the login View with the map in the shadows ZStack { - - ZStack { - Color.black.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight, alignment: .center).zIndex(1).opacity(0.5).ignoresSafeArea() - MapView() - .blur(radius: 10) - .allowsHitTesting(false) - } - - VStack(spacing: CGFloat(verticalPaddingForForm)) { - Text("Welcome To MBStillas ST") - .font(.title) - .bold() - HStack { - Image(systemName: "person") + /// Blur effect + Color.black.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight, alignment: .center).zIndex(1).opacity(0.5).ignoresSafeArea() + MapView() + .blur(radius: 10) + .allowsHitTesting(false) + } + + VStack(spacing: CGFloat(verticalPaddingForForm)) { + Text("Welcome To MBStillas ST") + .font(.title) + .bold() + HStack { + Image(systemName: "person") .foregroundColor(.secondary) - TextField("Enter your email", text: $email) + + /// The login email + TextField("Enter your email", text: $email) .foregroundColor(Color.black) .autocapitalization(.none) .disableAutocorrection(true) - } - .padding() - .background(Color.white) - .cornerRadius(10) - - HStack { - Image(systemName: "lock") + } + .padding() + .background(Color.white) + .cornerRadius(10) + + HStack { + Image(systemName: "lock") .foregroundColor(.secondary) - SecureField("Enter password", text: $password) - .foregroundColor(Color.black) - .autocapitalization(.none) - .disableAutocorrection(true) - } - .padding() - .background(Color.white) - .cornerRadius(10) - - - Button(action: { - guard !email.isEmpty, !password.isEmpty else { - return - } - viewModel.signIn(email: email, password: password) - }) { - Text("Sign in") - .frame(width: 150, height: 50, alignment: .center) + /// The login password masked + SecureField("Enter password", text: $password) + .foregroundColor(Color.black) + .autocapitalization(.none) + .disableAutocorrection(true) + } + .padding() + .background(Color.white) + .cornerRadius(10) + + /// Sends the login request given that both email and password are filled out + Button(action: { + guard !email.isEmpty, !password.isEmpty else { + return } + viewModel.signIn(email: email, password: password) + }) { + Text("Sign in") + .frame(width: 150, height: 50, alignment: .center) + } + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + + /// Button to redirect to Sign up + NavigationLink("Sign up", destination: SignUpView()) .foregroundColor(.white) - .background(Color.blue) + .padding() + .background(Color.black.opacity(0.2)) .cornerRadius(10) - - - NavigationLink("Sign up", destination: SignUpView()) - .foregroundColor(.white) - .padding() - .background(Color.black.opacity(0.2)) - .cornerRadius(10) - - }.padding(.horizontal, CGFloat(verticalPaddingForForm)) } + .padding(.horizontal, CGFloat(verticalPaddingForForm)) } } - /* - var body: some View { - NavigationView { - VStack { - TextField("Email", text: $email) - .autocapitalization(.none) - .disableAutocorrection(true) - SecureField("Password", text: $password) - .autocapitalization(.none) - .disableAutocorrection(true) - Button(action: { - guard !email.isEmpty, !password.isEmpty else { - return - } - viewModel.signIn(email: email, password: password) - - }) { - Text("Sign in") - } - NavigationLink("Sign up", destination: SignUpView()) - .padding() - } - .padding() - } - - //NavigationBarBottom() - }*/ + } } struct SignInView_Previews: PreviewProvider { diff --git a/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift b/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift index b76e9d87ddbbb0f7fb1e67da4b0e955e5be4027a..67b02d12822cef0b5b0533d10fd54fd6dd280407 100644 --- a/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift @@ -8,11 +8,17 @@ import SwiftUI import FirebaseAuth +/// **SignUpView** +/// The view responsible for the sign up page +/// Code taken and inspired from this youtube video and firebases authentication startup +/// https://www.youtube.com/watch?v=vPCEIPL0U_k +/// https://firebase.google.com/docs/auth/ios/start // TODO: Add body where user inputs data in addition to username and password struct SignUpView: View { @State var email = "" @State var password = "" + /// The model responsible for sign up @EnvironmentObject var viewModel: AppViewModel var body: some View { @@ -20,22 +26,23 @@ struct SignUpView: View { TextField("Email", text: $email) .autocapitalization(.none) .disableAutocorrection(true) + + /// SecureField to mask the password SecureField("Password", text: $password) .autocapitalization(.none) .disableAutocorrection(true) + + /// Sends the request to sign up to the system Button(action: { guard !email.isEmpty, !password.isEmpty else { return } viewModel.signUp(email: email, password: password) - }) { Text("Sign up") } } .padding() - - //NavigationBarBottom() } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift b/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift index 5f5c1d116a7b1e71cd928bfceccef98035f335e9..2863563b9d9645339cdd7d7c3609ef21275edc3b 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift @@ -7,7 +7,8 @@ import SwiftUI -// Blurred View +/// **BlurView** +/// A blur view struct BlurView: UIViewRepresentable { let style: UIBlurEffect.Style diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift index e13ea2b834cfbbe29df9c8796a997b29957266a4..60ead4b2b96d2e5e882df7b768b1d583f4f81887 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **CheckBoxRow** +/// Creates a checkbox row to be used in lists etc. struct CheckBoxRow: View { var title: String @Binding var selectedItems: Set<String> @@ -29,6 +31,8 @@ struct CheckBoxRow: View { } } +/// **CheckBoxView** +/// Creates the view of the checkboxes struct CheckBoxView: View { @Binding var checked: Bool @State var title: String diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift index 788238b206ad32fe97159a1fae544bf5c617dd8c..0a65c93d29c7f2ce192cc0b61595556444f05738 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **CircleImage** +/// Creates a circle frame with an image struct CircleImage: View { var image: Image diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift index 353899141372c0bead7f7435c589114ab930e44a..6d9d3d6408e34195fc4e81d4dbdde3f141d13a8f 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **CornedRadiusStyle** +/// Custom corner radius for corner struct CornerRadiusStyle: ViewModifier { var radius: CGFloat var corners: UIRectCorner @@ -33,9 +35,9 @@ extension View { ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners)) } } - +/* struct CornerRadius_Previews: PreviewProvider { static var previews: some View { CornerRadius() } -} +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CustomCorners.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CustomCorners.swift deleted file mode 100644 index ed7e837a6d1ecd7ff3333665c24235138a6b79bc..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CustomCorners.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// CustomCorners.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 24/03/2022. -// - -import SwiftUI - -struct CustomCorners: Shape { - var corners: UIRectCorner - var radius: CGFloat - - func path(in rect: CGRect) -> Path { - let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) - - return Path(path.cgPath) - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift index 3870646a990539dbd7e20eef1a2a3ab7845dd2cd..6224cd3409b45c58e142605861ee89d4b81a891a 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift @@ -7,12 +7,16 @@ import SwiftUI +/// **SearchBar** +/// UIViewRepresentable which adds a searchbar to a list in transfere scaffolding +/// This is taken from: /// https://roddy.io/2020/09/07/add-search-bar-to-swiftui-picker/ struct SearchBar: UIViewRepresentable { @Binding var text: String var placeholder: String + /// Makes a searchbar func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar { let searchBar = UISearchBar(frame: .zero) searchBar.delegate = context.coordinator @@ -23,6 +27,7 @@ struct SearchBar: UIViewRepresentable { return searchBar } + /// Updates the searchbar func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) { uiView.text = text } @@ -58,8 +63,10 @@ struct NoProjectSelected: TextFieldStyle { } } +/// **TextFieldEmpty** +/// Checks if the textfield is empty, if so - make the border red +/// Code taken from and inspired from: /// https://stackoverflow.com/questions/60379010/how-to-change-swiftui-textfield-style-after-tapping-on-it -/// struct TextFieldEmpty: TextFieldStyle { @Binding var empty: Bool func _body(configuration: TextField<Self._Label>) -> some View { @@ -72,23 +79,20 @@ struct TextFieldEmpty: TextFieldStyle { } } -// https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjzjJC2tMP3AhUnSfEDHSwjC-0QFnoECAUQAQ&url=https%3A%2F%2Fsanzaru84.medium.com%2Fswiftui-how-to-add-a-clear-button-to-a-textfield-9323c48ba61c&usg=AOvVaw1aPoAd3QYr5ByERti3mGWj -struct ClearButton: ViewModifier -{ +/// **ClearButton** +/// The clear button for the textfield +/// Code taken from and inspired from: https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjzjJC2tMP3AhUnSfEDHSwjC-0QFnoECAUQAQ&url=https%3A%2F%2Fsanzaru84.medium.com%2Fswiftui-how-to-add-a-clear-button-to-a-textfield-9323c48ba61c&usg=AOvVaw1aPoAd3QYr5ByERti3mGWj +struct ClearButton: ViewModifier { + /// The searchfield text @Binding var text: String - public func body(content: Content) -> some View - { - ZStack(alignment: .trailing) - { + public func body(content: Content) -> some View { + ZStack(alignment: .trailing) { content - if !text.isEmpty - { - Button(action: - { + if !text.isEmpty { + Button(action: { self.text = "" - }) - { + }) { Image(systemName: "delete.left") .foregroundColor(Color(UIColor.opaqueSeparator)) } @@ -97,3 +101,17 @@ struct ClearButton: ViewModifier } } } + +/// **NumbersOnly** +/// Validates textfield input to be Int +class NumbersOnly: ObservableObject { + @Published var value = "" { + didSet { + let filtered = value.filter { $0.isNumber } + + if value != filtered { + value = filtered + } + } + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift index 363c72774c941255801b18584f01dc620c45fe37..ff938d185bc29e2049dae4396fcfb865147ebda2 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift @@ -10,20 +10,22 @@ import MapKit import CoreLocation -/** - MapDisplay - makes a MKMapView and defines its properties like userTrackingMode and setRegion etc. - - This way to define a map was inspired by this resource, as it creates a MKMapView in a neat way. It also makes the process of displaying CheckPoints and GeoFences easier alongside with other map functionality. - https://iosapptemplates.com/blog/swiftui/map-view-swiftui - */ +/// **MapDisplay** +/// Makes a MKMapView and defines its properties like userTrackingMode and setRegion etc. +/// This way to define a map was inspired by this resource, as it creates a MKMapView in a neat way. It also makes the process of displaying CheckPoints and GeoFences easier alongside with other map functionality. +/// https://iosapptemplates.com/blog/swiftui/map-view-swiftui struct MapDisplay: UIViewRepresentable { /// A property wrapper type that instantiates an observable object of type MapViewModel() @StateObject private var viewModel = MapViewModel() - - /** - makeUIView() - Makes the MKMapView - Allows to show user location, sets tracking mode and region of interest on "open" - */ + @State var projects: [Project] = [Project]() + + + + + /// Makes the MKMapView + /// Allows to show user location, sets tracking mode and region of interest on "open" + /// - Parameter context: A context structure containing information about the current state of the system + /// - Returns: A MKMapView func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView(frame: UIScreen.main.bounds) mapView.showsUserLocation = true @@ -33,23 +35,24 @@ struct MapDisplay: UIViewRepresentable { return mapView } + /// Updates the MKMapView + /// - Parameter uiView: The MKMapView to be updated func updateUIView(_ uiView: MKMapView) { - let projVi = ProjectView() - - let annotations = projVi.projectsArr.map { project -> MKAnnotation in + //ProjectData().loadData(completion: @escaping projects) + let annotations = ProjectListView().projects.map { project -> MKAnnotation in let annotation = MKPointAnnotation() annotation.title = project.projectName annotation.subtitle = "\(project.projectID)" annotation.coordinate = CLLocationCoordinate2D(latitude: project.latitude, longitude: project.longitude) + print(annotation) return annotation } uiView.addAnnotations(annotations) } - /** - updateUIView() - Updates the state of the MKMapView with the changed information from SwiftUI - */ + /// **updateUIView** + /// Updates the state of the MKMapView with the changed information from SwiftUI func updateUIView(_ uiView: MKMapView, context: Context) { + } } - diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift index 93c229be08433816a3790550ad8e8f8b5d306c92..c7f5c0f7f3877d5ce42e71bb7cc832fc7aac7e88 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift @@ -9,15 +9,15 @@ import SwiftUI import UIKit import MapKit -/** - A MapView responsible for displaying the Apple Maps in the application. - */ +/// **MapView** +/// Is responsible for displaying the Apple Maps in the application. struct MapView: View { /// A property wrapper type that instantiates an observable object of type MapViewModel() @StateObject private var viewModel = MapViewModel() @State private var searchText = "" @State private var dismissedAlready = false - + //@State var projects = [Project]() + var body: some View { VStack { GeometryReader { proxy in @@ -27,6 +27,7 @@ struct MapView: View { .onAppear { viewModel.checkIfLocationServicesIsEnabled() } + /// Displays an alert to the user if the location services are disabled, recommending the user to enable them and suggesting a redirect to the location service settings for the application. /// This alert is inspired from: https://www.hackingwithswift.com/forums/swiftui/getting-error-when-trying-to-change-location-authorisation/9216 .alert(isPresented: $viewModel.locationPermissionDenied, @@ -40,20 +41,25 @@ struct MapView: View { secondaryButton: .cancel(Text("Dismiss"), action: { setLocationPermissionFalse() })) }) + /*.task { + await ProjectData().loadData { (projects) in + self.projects = projects + } + }*/ } } + } - /** - Dismisses the alert - */ + + /// Dismisses the alert func setLocationPermissionFalse() { viewModel.locationPermissionDenied = false } +} - struct MapView_Previews: PreviewProvider { - static var previews: some View { - MapView() - } +struct MapView_Previews: PreviewProvider { + static var previews: some View { + MapView() } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift index 78e89915f3e1df90d13dad040e04980ef5947e8d..8fcbbc2b966f4343138777dac3236046aaa71911 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift @@ -9,10 +9,9 @@ import Foundation import MapKit import SwiftUI -/** - MapDetails - enum values that are used multiple places. - Used to abstract reused code - */ +/// **MapDetails** +/// Enum values that are used multiple places. +/// Used to abstract reused code. enum MapDetails { /// Sets the starting location of the map to be Gjøvik (latitude: 60.79574, longitude: 10.69155) static let startingLocation = CLLocationCoordinate2D( @@ -20,18 +19,17 @@ enum MapDetails { longitude: 10.69155 ) /// Sets zoom level of map on initialization - /// Closer to zero is more zoomed in + /// Closer to zero is more zoomed in static let defaultSpan = MKCoordinateSpan( latitudeDelta: 0.03, longitudeDelta: 0.03 ) } -/** - Class responsible for checking if the user has enabled location services. - This class is inspired a lot by the Apple Development documentation as well as this youtube video: - https://www.youtube.com/watch?v=hWMkimzIQoU - */ +/// **MapViewModel** +/// Class responsible for checking if the user has enabled location services. +/// This class is inspired a lot by the Apple Development documentation as well as this youtube video: +/// https://www.youtube.com/watch?v=hWMkimzIQoU final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate { @Published var locationPermissionDenied = false @Published var dismissCount = 0 @@ -45,9 +43,7 @@ final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate var locationManager: CLLocationManager? - /** - checkIfLocationServicesIsEnabled() - Checks if the user has enabled location services. - */ + /// checkIfLocationServicesIsEnabled() - Checks if the user has enabled location services. func checkIfLocationServicesIsEnabled() { if CLLocationManager.locationServicesEnabled() { locationManager = CLLocationManager() @@ -61,9 +57,8 @@ final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate } // TODO: Check -> fatal error when location services are off? - /** - checkLocationAuthorization() - Checks which authorization the application is assigned to. - */ + /// checkLocationAuthorization + /// Checks which authorization the application is assigned to. private func checkLocationAutorization() { guard let locationManager = locationManager else { return } @@ -90,9 +85,7 @@ final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate } } - /** - locationManagerDidChangeAuthorization() - Checks the autorization on locationManager creation as well as if the apps authorization changes - */ + /// locationManagerDidChangeAuthorization() - Checks the autorization on locationManager creation as well as if the apps authorization changes func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { checkLocationAutorization() } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift index fa9b2b435b7140a9a8d292308dbb37b1d488bc16..618f05fe0355c1bdf3b23e41dc584eb087c35807 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift @@ -8,13 +8,21 @@ import SwiftUI import Foundation +/// **ProfileData** +/// Gets the data about the logged in user from the API class ProfileData: ObservableObject { + /// Is data loading? @Published private var _isLoadingProfile: Bool = false + /// Getter for the _isLoadingProfile var isLoadingProfile: Bool { get { return _isLoadingProfile} } + /// Responsible for getting the data about the logged in user from the API + /// - Parameters: + /// - userID: the userID of the logged in user from Firebase Authentication + /// - completion: completion handler func loadData(userID: String, completion:@escaping (Profile) -> ()) async { _isLoadingProfile = true print("One = \(_isLoadingProfile)") @@ -23,6 +31,7 @@ class ProfileData: ObservableObject { print("Invalid url...") return } + /// Sends the request and gets the data URLSession.shared.dataTask(with: url) { [self] data, response, error in let profile = try! JSONDecoder().decode(Profile.self, from: data!) DispatchQueue.main.async { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift index 9c7563e71fc5d030d3dcc671332772b7b064e466..4fc8cd32fa84f409afbfa881043ebe4b969b6e50 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift @@ -8,14 +8,21 @@ import SwiftUI import Foundation +/// **ProjectData** +/// Retrieves all the projects and its data from the API class ProjectData: ObservableObject { - @Published var projects = [Project]() // TODO: REMOVE IF NOT USED IN THE END + //@Published var projects = [Project]() // TODO: REMOVE IF NOT USED IN THE END + /// Is data loading? @Published private var _isLoading: Bool = false + /// Getter for the _isLoading var isLoading: Bool { get { return _isLoading} } + + /// Responsible for getting the data about projects from the API + /// - Parameter completion: completion handler func loadData(completion:@escaping ([Project]) -> ()) async { _isLoading = true print("One = \(_isLoading)") @@ -24,6 +31,7 @@ class ProjectData: ObservableObject { print("Invalid url...") return } + /// Sends the request and gets the data URLSession.shared.dataTask(with: url) { [self] data, response, error in let projects = try! JSONDecoder().decode([Project].self, from: data!) DispatchQueue.main.async { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/ModelData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/ModelData.swift deleted file mode 100644 index f88d42b51518fef626ea99ec99adead88f0afed7..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/ModelData.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ModelData.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 28/03/2022. -// - -import Foundation -import Combine - -/** - ModelData responsible for loading/decoding the user information from the data/json object - To update views when data changes, you make your data model classes observable objects. - */ -final class ModelData: ObservableObject { - @Published var users: [User] = load("userData.json") -} - -/// load<T: Decodable>() - function for decoding the data into json object -/// Uses guard and do/catch methods to make sure to catch potential errors while retreiving or decoding the data -func load<T: Decodable>(_ filename: String) -> T { - let data: Data - - guard let file = Bundle.main.url(forResource: filename, withExtension: nil) - else { - fatalError("Couldn't find \(filename) in main bundle.") - } - - do { - data = try Data(contentsOf: file) - } catch { - fatalError("Couldn't load \(filename) from main bundle:\n\(error)") - } - - do { - let decoder = JSONDecoder() - return try decoder.decode(T.self, from: data) - } catch { - fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift index 6347641454bafad8dadf65cfed509b4767a7bd9a..c8c45a412044c311bf7e17e74a4bec3bf642cb39 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift @@ -7,7 +7,6 @@ import Foundation -// MARK: - Profile struct Profile: Codable { let employeeID: String var name: Name @@ -17,7 +16,6 @@ struct Profile: Codable { let admin: Bool } -// MARK: - Name struct Name: Codable { let firstName, lastName: String } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift index b466a92b304949f37447567d367c1302cd9e42da..0d5a17cb6a487a2af73d1c8033f3339bfe140ae0 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift @@ -8,13 +8,6 @@ import Foundation import SwiftUI -/* -struct Response: Codable{ - var results: [Project] -} - */ - -// MARK: - Project struct Project: Codable { let projectID: Int let projectName: String @@ -28,19 +21,16 @@ struct Project: Codable { let scaffolding: [Scaffolding]? } -// MARK: - Address struct Address: Codable { let street, zipcode, municipality, county: String } -// MARK: - Customer struct Customer: Codable { let name: String let number: Int let email: String } -// MARK: - Geofence struct Geofence: Codable { let wPosition, xPosition, yPosition, zPosition: Position @@ -52,17 +42,14 @@ struct Geofence: Codable { } } -// MARK: - Position struct Position: Codable { let latitude, longitude: Double } -// MARK: - Period struct Period: Codable { let startDate, endDate: String } -// MARK: - Scaffolding struct Scaffolding: Codable { let type: String let quantity: Quantity @@ -73,7 +60,6 @@ struct Scaffolding: Codable { } } -// MARK: - Quantity struct Quantity: Codable { let expected, registered: Int } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift index 967617d87d8a8ec53311e8fb3dec41ebacbee3d4..fced020bdbab9158a61a32f22f5ceea4789712ba 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift @@ -7,13 +7,11 @@ import Combine -// MARK: - Scaff struct Scaff: Codable { let scaffold: [Move] let toProjectID, fromProjectID: Int } -// MARK: - Move struct Move: Codable { let type: String let quantity: Int diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift index 717f0ecb01331fef9df16eb57c17b42647fac486..ad6c33d1604fde689e12390a38c6d011c79515bd 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift @@ -8,9 +8,6 @@ import SwiftUI import Foundation -/** - User - decode Data into struct - */ struct User: Codable, Hashable, Identifiable { var id: Int var name: String diff --git a/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift b/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift index fda458bfaa835239596bab9b28456e48c3101a4f..c4e048d871c94e05465428417d72ba60f312f5b9 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift @@ -7,19 +7,23 @@ import SwiftUI +/// **NavigationBarBottom** +/// The navigationbar View responsible for the navigation between the three main views Project, Map and Profile struct NavigationBarBottom: View { - @State private var selection: Tab = .map + /// Sets selection to project + @State private var selection: Tab = .project + + /// All projects @State var projects = [Project]() + + /// Enum for the different pages enum Tab { case project case map case profile - case projectViewAPI } - var body: some View { - TabView(selection: $selection) { ProjectListView() .tabItem { @@ -40,7 +44,7 @@ struct NavigationBarBottom: View { .tag(Tab.profile) } .onAppear() { - /// https://www.bigmountainstudio.com/community/public/posts/86559-how-to-customize-the-background-of-the-tabview-in-swiftui + /// The transparrent effect is taken from: https://www.bigmountainstudio.com/community/public/posts/86559-how-to-customize-the-background-of-the-tabview-in-swiftui let appearance = UITabBarAppearance() appearance.backgroundEffect = UIBlurEffect(style: .systemThinMaterial) // Use this appearance when scrolling behind the TabView: @@ -54,6 +58,6 @@ struct NavigationBarBottom: View { struct NavigationBarBottom_Previews: PreviewProvider { static var previews: some View { NavigationBarBottom() - .environmentObject(ModelData()) + //.environmentObject(ModelData()) } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift index 219f4e0222662ed89c10e97510a6144497ac8850..ab195642e6b3d88083b5198381ce080f8c9c7f63 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift @@ -7,9 +7,8 @@ import SwiftUI -/** - ProfileView - Calls the ProfileDetails view containing information about a user - */ +/// **ProfileView** +/// Calls the ProfileDetails View containing information about a user struct ProfileView: View { var body: some View { VStack { @@ -18,13 +17,17 @@ struct ProfileView: View { } } -/** - ProfileDetails - A view responsible for the layout of the user information and showing the details about the user - */ +/// **ProfileDetails** +/// A View responsible for the layout of the user information and showing the details about the user struct ProfileDetails: View { + /// Darkmode or lightmode? + @Environment(\.colorScheme) var colorScheme + + /// Models @EnvironmentObject var viewModel: AppViewModel @ObservedObject var profileModel: ProfileData = ProfileData() + /// Initializes a user object @State var user: [Profile] = [Profile]() var body: some View { @@ -38,52 +41,127 @@ struct ProfileDetails: View { .offset(y: -130) .padding(.bottom, -130) + /// If there are user data if (!user.isEmpty) { - /// A VStack used to display all the user profile data - VStack(alignment: .leading) { - HStack { - Text("\(user[0].name.firstName) \(user[0].name.lastName)") - .font(.largeTitle) - } - - HStack { - Text("MBStillas") - //.font(.subheadline) - Spacer() - Text("Rolle: \(user[0].role)") - //.font(.subheadline) - } - //.font(.subheadline) - .foregroundColor(.secondary) - Divider() - VStack { - Text("Fødselsdato") - .font(.title2) - Text("\(user[0].dateOfBirth)") - .foregroundColor(.secondary) + VStack { + Image(systemName: "person.crop.circle.badge.checkmark") + .resizable() + .frame(width: 35, height: 30) + .foregroundColor(.blue) + + Text("Bruker info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du brukerinformasjonen din.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + HStack { + Text("\(user[0].name.firstName) \(user[0].name.lastName)") + } + .font(.title3.bold()) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].employeeID)") + .font(.body) + + Text("ANSATT NUMMER") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].dateOfBirth)") + .font(.body) + + Text("FØDSELSNUMMER") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].role)") + .font(.body) + + Text("ROLLE") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].admin.description)") + .font(.body) + + Text("ADMIN") + .foregroundColor(.gray) + .font(.system(size: 15)) + } } - } - .padding() + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) - Spacer() - - Button (action: { - viewModel.signOut() - }) { - Text("Logg av") - .frame(width: 150, height: 50, alignment: .center) - } - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(10) - - Spacer() - .frame(height:50) // limit spacer size by applying a frame + VStack { + VStack { + Image(systemName: "person.circle") + .resizable() + .frame(width: 30, height: 30) + .foregroundColor(.blue) + + Text("Kontakt info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du kontaktinformasjonen din.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + Text("\(user[0].phone)") + .font(.body) + + Text("TELEFONNUMMER") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].email)") + .font(.body) + + Text("EMAIL") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + } + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) } } .task { + /// Get user data from the API with the logged in users ID await profileModel.loadData(userID: viewModel.userID) { (user) in self.user.append(user) } @@ -92,94 +170,6 @@ struct ProfileDetails: View { } } -/* -/** - ProfileView - Calls the ProfileDetails view containing information about a user - */ -struct ProfileView: View { - var body: some View { - VStack { - // TODO: Change input to not take the ModelData's first element only, but get info from API - ProfileDetails(user: ModelData().users[0]) - } - } -} - -/** - ProfileDetails - A view responsible for the layout of the user information and showing the details about the user - */ -struct ProfileDetails: View { - @EnvironmentObject var modelData: ModelData - @EnvironmentObject var viewModel: AppViewModel - - var user: User - - /// Retrieves the user from the json object with ID equal to the object passed into the voew - var userIndex: Int { - modelData.users.firstIndex(where: { $0.id == user.id })! - } - - var body: some View { - ScrollView { - /// MapView displaying the map in the top of the screen - MapView() - .ignoresSafeArea(edges: .top) - .frame(height: 300) - /// CircleImage responsible for displaying the user profile image - CircleImage(image: user.image) - .offset(y: -130) - .padding(.bottom, -130) - - /// A VStack used to display all the user profile data - VStack(alignment: .leading) { - HStack { - Text(user.name) - .font(.largeTitle) - } - - HStack { - // TODO: Change to not hard coded values when API is updated - Text("MBStillas") - //.font(.subheadline) - Spacer() - Text("Rolle: \(user.role)") - //.font(.subheadline) - } - //.font(.subheadline) - .foregroundColor(.secondary) - - Divider() - - VStack { - Text("Fødselsdato") - .font(.title2) - Text("\(user.dateOfBirth)") - .foregroundColor(.secondary) - } - } - .padding() - - Spacer() - - Button (action: { - viewModel.signOut() - }) { - Text("Logg av") - .frame(width: 150, height: 50, alignment: .center) - } - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(10) - - Spacer() - .frame(height:50) // limit spacer size by applying a frame - } - .navigationTitle(user.name) - .navigationBarTitleDisplayMode(.inline) - .ignoresSafeArea(edges: .top) - } -}*/ - struct ProfileView_Previews: PreviewProvider { static var previews: some View { ProfileView() diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift index 73444efb81381bdd24e30ab85582f7c9789f9277..98e3bb5ec1136b2aef72a14cd1472cf8403a6007 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift @@ -7,16 +7,26 @@ import SwiftUI +/// **FilterProjectArea** +/// The View for selecting a project with area set to a value struct FilterProjectArea: View { + + /// Is the box checked? @State private var checked: [Bool] + + /// All the selected counties @Binding var selArr: [String] + + /// Area filter active @Binding var areaFilterActive: Bool + /// All counties to filter based of let counties = ["Agder", "Innlandet", "Møre og Romsdal", "Nordland", "Oslo", "Rogaland", "Vestfold og Telemark", "Troms og Finnmark", "Trøndelag", "Vestlandet", "Viken"] - // selectedItems gets updated by the CheckBoxRow as it changes - @State var selectedItems: Set<String> = [] // Use a Set to keep track of multiple check boxes + /// selectedItems gets updated by the CheckBoxRow as it changes + @State var selectedItems: Set<String> = [] /// Use a Set to keep track of multiple check boxes + /// Initializes the selections to false so the boxes are unchecked as the user accesses the filter init(selArr: Binding<[String]>, areaFilterActive: Binding<Bool>) { self._selArr = selArr _checked = State(initialValue: [Bool](repeating: false, count: counties.count)) @@ -27,6 +37,7 @@ struct FilterProjectArea: View { VStack { VStack { List { + /// For each county, add it to the list with a checkbox and description ForEach(counties, id: \.self) { county in HStack { CheckBoxRow(title: county, selectedItems: $selectedItems, isSelected: selectedItems.contains(county)) @@ -40,6 +51,7 @@ struct FilterProjectArea: View { .padding(.bottom, 110) } .overlay(alignment: .bottom) { + /// Updates the parent View with the selected counties Button(action: { print(self.selectedItems) for selectedItem in selectedItems { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterProjectData.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterProjectData.swift deleted file mode 100644 index 1e62eea9aaf6c92c72b0fc0ce982a55fbd1fde07..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterProjectData.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// FilterProjectData.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 14/04/2022. -// - -import UIKit -import SwiftUI -/* -enum FilterType { - case none, - period, - startBeforePeriod, - startAfterPeriod, - endBeforePeriod, - endAfterPeriod, - sizeEqualTo, - sizeLessThan, - sizeGreaterThan, - state, - county -}*/ - -struct FilterProjectData: View { - @State var projects = [Project]() - @State private var showFilterModalView: Bool = false - @State private var showAddProjectModalView: Bool = false - @State var filterArrArea: [String] = [] - - @State var filter: FilterType = .none - @State var filterArr: [String] = [] - - // TODO: Make these values operable - @State var projectStartDate = Date.distantPast - @State var projectEndDate = Date.distantFuture - @State var projectSize = 99999 - @State var projectState = "Active" - @State var projectCounty = "Innlandet" - - var body: some View { - VStack { - NavigationView { - Form { - Section(header: Text("All Projects")) { - List(filteredProjects, id: \.projectID) { project in - Text(project.projectName) - } - .navigationTitle("Projects") - //.listStyle(.grouped) - } - } - .listStyle(.grouped) - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - Button(action: { - print("Filter tapped!") - self.showFilterModalView.toggle() - - }) { - Label("Filter", systemImage: "line.3.horizontal.decrease.circle") - } - } - - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button(action: { - print("Add project tapped!") - self.showAddProjectModalView.toggle() - }) { - Label("Add", systemImage: "plus.circle") - } - } - } - .sheet(isPresented: $showFilterModalView, - onDismiss: didDismiss) { - FilterView(selStartDateBind: $projectStartDate, selEndDateBind: $projectEndDate, projectArea: $projectCounty, projectSize: $projectSize, projectStatus: $projectState, filterArr: $filterArr, filterArrArea: $filterArrArea) - .onChange(of: projectStartDate) { value in - filter = .period - } - } - .sheet(isPresented: $showAddProjectModalView, onDismiss: didDismiss) { - AddProjectView() - } - } - } - .task { - await ProjectData().loadData { (projects) in - self.projects = projects - } - } - } - - func didDismiss() { - - // Handle the dismissing action. - } - - var filteredProjects: [Project] { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd/MM/yy" - - switch filter { - case .none: - return projects - case .period: - //return projects.filter { $0.period.startDate > projectStartDate && $0.period.endDate < projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! > projectStartDate && dateFormatter.date(from: $0.period.endDate)! < projectEndDate } - case .startBeforePeriod: - //return projects.filter { $0.period.startDate < projectStartDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! < projectStartDate } - case .startAfterPeriod: - //return projects.filter { $0.period.startDate > projectStartDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! > projectStartDate } - case .endBeforePeriod: - //return projects.filter { $0.period.endDate < projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.endDate)! < projectEndDate } - case .endAfterPeriod: - //return projects.filter { $0.period.endDate > projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.endDate)! > projectEndDate } - case .sizeEqualTo: - return projects.filter { $0.size == Int(projectSize) } - case .sizeLessThan: - return projects.filter { $0.size < Int(projectSize) } - case .sizeGreaterThan: - return projects.filter { $0.size > Int(projectSize) } - case .state: - return projects.filter { $0.state == projectState } - case .county: - return projects.filter { $0.address.county == projectCounty } - } - } -} - -struct AddProjectView: View { - var body: some View { - VStack { - Text("Add Project SheetView") - } - } -} -/* -struct FilterProjectData_Previews: PreviewProvider { - static var previews: some View { - FilterProjectData() - } -}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift index 262b8c9aa5d19796af766ec3a5de3e986a23251c..3f98a295f5187151c11b25ae3b68844de6d0fd70 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift @@ -7,6 +7,9 @@ import SwiftUI + +/// **ActiveAreaFilterView** +/// The view presented on top of the area-navigation row to display a preview of the selected areafilter. struct ActiveAreaFilterView: View { @Binding var filterArr: [String] @@ -14,36 +17,32 @@ struct ActiveAreaFilterView: View { var body: some View { + /// if there is a filter applied, display a preview of the selected filter if areaFilterActive { HStack { HStack { - //ScrollViewReader { scrollView in - ScrollView (.horizontal, showsIndicators: false) { - HStack { - Text("(\(filterArr.count))") - .padding(.leading, 4) - ForEach(filterArr.indices, id: \.self) { index in - HStack { - Text("\(filterArr[index])") - .lineLimit(1) - .padding(-3) - if index != filterArr.count-1 { - Text(",") - } + ScrollView (.horizontal, showsIndicators: false) { + HStack { + Text("(\(filterArr.count))") + .padding(.leading, 4) + ForEach(filterArr.indices, id: \.self) { index in + HStack { + Text("\(filterArr[index])") + .lineLimit(1) + .padding(-3) + if index != filterArr.count-1 { + Text(",") } } } } - /* - .onAppear { - scrollView.scrollTo(filterArr[filterArr.endIndex]) - } - }*/ + } } .font(.system(size: 11).bold()) .padding(.vertical, 5) .lineLimit(1) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "area") self.areaFilterActive = false @@ -62,6 +61,8 @@ struct ActiveAreaFilterView: View { } } + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift index 4f94152c3082cf8face639731661163ce7ee8f0f..c0411c2bec6eddc18136d0efd3ca3bf0a551bd86 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **ActivePeriodFilterView** +/// The view presented on top of the size-navigation row to display a preview of the selected periodfilter. struct ActivePeriodFilterView: View { @Binding var startDate: Date @Binding var endDate: Date @@ -17,6 +19,7 @@ struct ActivePeriodFilterView: View { var body: some View { + /// if there is a filter applied, display a preview of the selected filter if periodFilterActive { HStack { HStack { @@ -33,6 +36,7 @@ struct ActivePeriodFilterView: View { .font(.system(size: 11).bold()) .padding(.vertical, 5) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "period") self.periodFilterActive.toggle() @@ -50,6 +54,8 @@ struct ActivePeriodFilterView: View { } } + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift index e077658847652b7b8d84e58d82f4709714a5b787..a8477ed2756ad105365c980a7fc15bf1f7f50138 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **ActiveSizeFilterView** +/// The view presented on top of the size-navigation row to display a preview of the selected sizefilter. struct ActiveSizeFilterView: View { @Binding var filterArr: [String] @Binding var projectMinSize: Int @@ -15,12 +17,12 @@ struct ActiveSizeFilterView: View { @Binding var selection: String var body: some View { - + /// if there is a filter applied, display a preview of the selected filter if sizeFilterActive { HStack { HStack { ScrollView (.horizontal, showsIndicators: false) { - if selection == "Between" { + if selection == "Between" { /// display both minimum size and max size HStack { Text("\(projectMinSize) m") + Text("2") @@ -34,7 +36,7 @@ struct ActiveSizeFilterView: View { .font(Font.system(size: 10)) } .padding(.leading, 5) - } else if selection == "Less Than" { + } else if selection == "Less Than" { /// display only minimum size HStack { Text("Under ") + Text("\(projectMinSize) m") @@ -43,7 +45,7 @@ struct ActiveSizeFilterView: View { .font(Font.system(size: 10)) } .padding(.leading, 5) - } else if selection == "Greater Than" { + } else if selection == "Greater Than" { /// display only maximum size HStack { Text("Over ") + Text("\(projectMaxSize) m") @@ -59,6 +61,7 @@ struct ActiveSizeFilterView: View { .padding(.vertical, 5) .lineLimit(1) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "size") self.sizeFilterActive = false @@ -78,6 +81,9 @@ struct ActiveSizeFilterView: View { } } + + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift index b7a5e9e4e7c3f1df9823363905e992d47ae4417d..deac93f86ff5057782446d10f365fd4b65f56f19 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift @@ -7,13 +7,15 @@ import SwiftUI +/// **ActiveStatusFilterView** +/// The view presented on top of the size-navigation row to display a preview of the selected statusfilter. struct ActiveStatusFilterView: View { @Binding var filterArr: [String] @Binding var projectStatus: String @Binding var statusFilterActive: Bool var body: some View { - + /// if there is a filter applied, display a preview of the selected filter if statusFilterActive { HStack { HStack { @@ -24,6 +26,7 @@ struct ActiveStatusFilterView: View { .font(.system(size: 11).bold()) .padding(.vertical, 5) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "status") self.statusFilterActive.toggle() @@ -41,6 +44,8 @@ struct ActiveStatusFilterView: View { } } + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift index fffb8620e52ff427e5a49e7f79373d10e062c585..267015dcfb6caa2a56257769a2b21b3b0281c0e7 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift @@ -9,10 +9,13 @@ import SwiftUI // TODO: Add enum for switch case instead of hard-coded values +/// **FilterView** +/// Creates a View displaying the a NavigationView for the different project filters. struct FilterView: View { - // TODO: Add buttons for switching between start before only, period between etc. + /// Filter options @State private var filterItems = ["Område", "Periode", "Størrelse", "Status"] + /// All the different filtervalues @Binding var selStartDateBind: Date @Binding var selEndDateBind: Date @Binding var projectArea: String @@ -20,35 +23,41 @@ struct FilterView: View { @Binding var projectStatus: String @Binding var minProjectSize: Int @Binding var maxProjectSize: Int - // TODO: DENNA TINGEN HER ISTEDENFOR scoreFrom - @Binding var sizeSortType: String + /// Returns whether a filter is active or not @State var periodFilterActive: Bool = false @State var areaFilterActive: Bool = false @State var sizeFilterActive: Bool = false @State var statusFilterActive: Bool = false + /// Array of all the filters active @Binding var filterArr: [String] + + /// All selected areas to be filtered on @Binding var filterArrArea: [String] + /// Selected filter start date and end date @State var selStartDate = Date() @State var selEndDate = Date() var body: some View { NavigationView { List { + /// For each filter type, add it to the List with its respective navigation destination ForEach(filterItems, id: \.self) { filterItem in NavigationLink { switch filterItem { case "Område": FilterProjectArea(selArr: $filterArrArea, areaFilterActive: $areaFilterActive) .onAppear { + /// Resets the filter filterArrArea.removeAll() } case "Periode": FilterProjectPeriod(selStartDateBind: $selStartDate, selEndDateBind: $selEndDate, periodFilterActiveBind: $periodFilterActive) .onAppear { + /// Resets the filter selStartDateBind = Date.distantPast selEndDateBind = Date.distantFuture if (selStartDateBind != Date.distantPast || selEndDateBind != Date.distantFuture) { @@ -75,13 +84,13 @@ struct FilterView: View { FilterProjectStatus(filterArr: $filterArr, selection: $projectStatus) .onChange(of: projectStatus) { status in projectStatus = status - print(projectStatus) statusFilterActive = true } default: Text("No views available") } } label: { + /// Adds label to list as well as adding the filter preview to the list item if it is active HStack { Text(filterItem) Spacer() @@ -132,6 +141,7 @@ struct FilterView: View { .navigationTitle(Text("Filter")) .navigationViewStyle(StackNavigationViewStyle()) .overlay(alignment: .bottom) { + /// Adds and removes active filters based on current selection Button(action: { // TODO: Change to use for loop? /*for filterItem in filterArr { @@ -157,8 +167,6 @@ struct FilterView: View { } else { deleteFilterItem(filterItem: "status") } - - print(filterArr) }) { Text("Bruk") .frame(width: 300, height: 50, alignment: .center) @@ -171,12 +179,17 @@ struct FilterView: View { } } + + /// If the selected filter is newly added and not only updated, add it to the array of filters + /// - Parameter filterItem: the selected filter you want to add func addFilterItem(filterItem: String){ if !filterArr.contains(filterItem) { filterArr.append(filterItem) } } + /// Remove filter item from the array of filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift index 11ab17de827f07d62284d420655bba25ec7e52df..a922ae48c29895d6105f5957a3d49789e4610282 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift @@ -7,11 +7,17 @@ import SwiftUI +/// **CalendarView** +/// Displays the period filter View with two calendars for the user to interact with. struct CalendarView: View { + /// The selected start date and end date @Binding var selStartDate: Date @Binding var selEndDate: Date + + /// Checks if the period filter is active @Binding var periodFilterActive: Bool + /// Base values for the calendar @State private var startDate = Date() @State private var endDate = Date() @@ -21,6 +27,7 @@ struct CalendarView: View { VStack { Section { VStack { + /// First calendar DatePicker( "Start dato", selection: $startDate, @@ -30,6 +37,7 @@ struct CalendarView: View { Divider() + /// Second calendar DatePicker( "Slutt dato", selection: $endDate, @@ -43,14 +51,12 @@ struct CalendarView: View { .padding(.top, 40) } Spacer() + + /// Returns the selected dates to the parent View Button(action: { selStartDate = $startDate.wrappedValue selEndDate = $endDate.wrappedValue periodFilterActive = true - print("______") - print(selStartDate) - print(selEndDate) - print("______") }) { Text("Bruk") .frame(width: 300, height: 50, alignment: .center) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift index fb905174bc0c3826e430137c973aea669dafa20e..31f8debd43c19c1bb8de1d1403e293e6a99f9405 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift @@ -7,20 +7,27 @@ import SwiftUI +/// **FilterProjectPeriod** +/// The View for selecting a period filter struct FilterProjectPeriod: View { - //@State private var date = Date() + /// Selected start date and end date @Binding var selStartDateBind: Date @Binding var selEndDateBind: Date + + /// Tells if filter is activated or not @Binding var periodFilterActiveBind: Bool @State var periodFilterActive: Bool = true + + /// Initializes the start date and end date to be the date of the day @State private var selStartDate = Date() @State private var selEndDate = Date() - var body: some View { VStack { + /// CalendarView with calendars for both start date and end date CalendarView(selStartDate: $selStartDate, selEndDate: $selEndDate, periodFilterActive: $periodFilterActive) .onAppear { + /// Resets the calendars selected date selStartDateBind = selStartDate selEndDateBind = selEndDate } @@ -34,7 +41,6 @@ struct FilterProjectPeriod: View { } } .navigationTitle(Text("Prosjekt periode")) - //.ignoresSafeArea(edges: .top) } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift index 3960d4bf85dcff27019a7cbf8e6de8331a8902aa..d108f6965516c925cfac72d8de2912b9753f1faf 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift @@ -7,24 +7,34 @@ import SwiftUI +/// **SizeGreaterThanFilter** +/// The View for selecting a project with size greater than a values struct SizeGreaterThanFilter: View { + + /// Enum for input enum Field: Int, CaseIterable { case input } - + + /// Size filter active @Binding var sizeFilterActive: Bool + /// Has textfield been activated and should be in focus? @FocusState var focusedField: Field? + /// Slider values @State var scoreTo: Int = 1000 @Binding var scoreToBind: Int - + + /// The input of minimum size @ObservedObject var input = NumbersOnly() + /// Slider data var sliderSizeMin = 100.0 var sliderSizeMax = 1000.0 var stepLength = 50.0 + /// Slider value var intProxyS2: Binding<Double>{ Binding<Double>( get: { @@ -43,8 +53,8 @@ struct SizeGreaterThanFilter: View { HStack { VStack { Text("Over") - HStack { + /// Adds textfield with bind to slider TextField("\(Int(sliderSizeMax))", text: $input.value) .font(Font.system(size: 30, design: .default)) .onChange(of: input.value) { value in @@ -92,7 +102,6 @@ struct SizeGreaterThanFilter: View { .font(Font.system(size: 60, design: .default)) VStack { - VStack (alignment: .leading) { HStack { Text("Fra") @@ -102,6 +111,7 @@ struct SizeGreaterThanFilter: View { .font(Font.system(size: 20, design: .default)) .padding(.top, 20) + /// Adds slider for minimum size Slider(value: intProxyS2 , in: sliderSizeMin...sliderSizeMax, step: stepLength, onEditingChanged: {_ in print(scoreTo.description) }) @@ -111,6 +121,8 @@ struct SizeGreaterThanFilter: View { } } Spacer() + + /// Returnerer den brukte størrelsedataen til parent Viewen Button(action: { print("Bruk") scoreTo = Int(input.value) ?? 1000 diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift index 2dff321abd2e455ae8c7f6ddd2df73ab7f69f7d6..31fc1b3d7bbe9665fa63c8bd4582bbe65fa41314 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift @@ -7,23 +7,34 @@ import SwiftUI +/// **SizeLessThanFilter** +/// The View for selecting a project with size less than a values struct SizeLessThanFilter: View { + + /// Enum for input enum Field: Int, CaseIterable { case input } + /// Size filter active @Binding var sizeFilterActive: Bool + /// Has textfield been activated and should be in focus? @FocusState var focusedField: Field? + /// First slider values @State var scoreFrom: Int = 100 @Binding var scoreFromBind: Int + + /// The input of maximum size @ObservedObject var input = NumbersOnly() + /// Slider data var sliderSizeMin = 100.0 var sliderSizeMax = 1000.0 var stepLength = 50.0 + /// Slider value var intProxyS1: Binding<Double>{ Binding<Double>( get: { @@ -42,8 +53,8 @@ struct SizeLessThanFilter: View { HStack { VStack { Text("Under") - HStack { + /// Adds textfield with bind to slider TextField("\(Int(sliderSizeMin))", text: $input.value) .font(Font.system(size: 30, design: .default)) .onChange(of: input.value) { value in @@ -91,6 +102,7 @@ struct SizeLessThanFilter: View { .font(Font.system(size: 20, design: .default)) .padding(.top, 20) + /// Adds slider for maximum size Slider(value: intProxyS1 , in: sliderSizeMin...sliderSizeMax, step: stepLength, onEditingChanged: {_ in print(scoreFrom.description) }) @@ -99,23 +111,25 @@ struct SizeLessThanFilter: View { } } } - Spacer() - Button(action: { - print("Bruk") - scoreFrom = Int(input.value) ?? 100 - scoreFromBind = scoreFrom - sizeFilterActive = true - }) { - Text("Bruk") - .frame(width: 300, height: 50, alignment: .center) + .overlay(alignment: .bottom) { + Spacer() + + /// Returnerer den brukte størrelsedataen til parent Viewen + Button(action: { + print("Bruk") + scoreFrom = Int(input.value) ?? 100 + scoreFromBind = scoreFrom + sizeFilterActive = true + }) { + Text("Bruk") + .frame(width: 300, height: 50, alignment: .center) + } + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + .padding(.bottom, 50) } - .foregroundColor(.white) - //.padding(.vertical, 10) - .background(Color.blue) - .cornerRadius(10) - - Spacer() - .frame(height:50) // limit spacer size by applying a frame + .ignoresSafeArea(.keyboard) } } /* diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift index a78ff02b2be8286d649217a664c23745a7b61be7..a0c16563b4106559f028d1b50052de285f09863d 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift @@ -7,27 +7,30 @@ import SwiftUI -/*enum SwipeHorizontalDirection: String { - case left, right, none - }*/ - +/// **FilterProjectSize** +/// Responsible for switching between the different filtering Views struct FilterProjectSize: View { + /// Slider values @State var scoreFrom: Int = 100 @State var scoreTo: Int = 1000 + /// Slider selection to be returned to parent View @Binding var scoreFromBind: Int @Binding var scoreToBind: Int + /// Size filter active @Binding var sizeFilterActive: Bool + + /// The selected size filtering method (Mindre enn, Mellom, Større enn) @Binding var selection: String - //@State var swipeHorizontalDirection: SwipeHorizontalDirection = .none { didSet { print(swipeHorizontalDirection) } } + /// Filtrering metoder let sizeSelections = ["Mindre enn", "Mellom", "Større enn"] var body: some View { - VStack { VStack { + /// Picker for velging av filtrering metode Picker("Select a state: ", selection: $selection) { ForEach(sizeSelections, id: \.self) { Text($0) @@ -40,21 +43,14 @@ struct FilterProjectSize: View { switch selection { case "Mindre enn": + /// Redirects to the SizeLessThanFilter View SizeLessThanFilter(sizeFilterActive: $sizeFilterActive, scoreFrom: scoreFrom, scoreFromBind: $scoreFromBind) .onChange(of: scoreFrom) { val in scoreFromBind = val sizeFilterActive = true } - /*.gesture(DragGesture().onChanged { - if $0.startLocation.x == $0.location.x { - self.swipeHorizontalDirection = .none - selection = selection - } else if $0.startLocation.x > $0.location.x { - self.swipeHorizontalDirection = .right - selection = "Between" - } - }).transition(.asymmetric(insertion: .scale, removal: .opacity))*/ case "Mellom": + /// Redirects to the SizeBetweenFilter View SizeBetweenFilter(sizeFilterActive: $sizeFilterActive, scoreFrom: scoreFrom, scoreFromBind: $scoreFromBind, scoreTo: scoreTo, scoreToBind: $scoreToBind) .onChange(of: scoreTo) { val in scoreToBind = val @@ -65,6 +61,7 @@ struct FilterProjectSize: View { sizeFilterActive = true } case "Større enn": + /// Redirects to the SizeGreaterThanFilter View SizeGreaterThanFilter(sizeFilterActive: $sizeFilterActive, scoreTo: scoreTo, scoreToBind: $scoreToBind) .onChange(of: scoreFrom) { val in scoreFromBind = val @@ -85,17 +82,6 @@ extension UIScreen { static let screenSize = UIScreen.main.bounds.size } -class NumbersOnly: ObservableObject { - @Published var value = "" { - didSet { - let filtered = value.filter { $0.isNumber } - - if value != filtered { - value = filtered - } - } - } -} /* struct FilterProjectSize_Previews: PreviewProvider { static var previews: some View { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift index 95b182ea262bc3d9e1a87baaea97c91656bf548a..7166c5bba0d6661bdfb85e7ef04bfca5d47f30d9 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift @@ -7,49 +7,57 @@ import SwiftUI +/// **FilterProjectStatus** +/// The View for selecting a status filter struct FilterProjectStatus: View { + /// Array of all the filters active @Binding var filterArr: [String] + + /// The selected filter @Binding var selection: String - let states = ["Inactive", "Active", "Upcomming"] + /// The different project states + let states = ["Inactive", "Active", "Upcomming"] - var body: some View { - VStack { - Text("Select project status") - .font(.headline) - .padding(.vertical, 5) - - Picker("Select a state: ", selection: $selection) { - ForEach(states, id: \.self) { - Text($0) - } + var body: some View { + VStack { + Text("Select project status") + .font(.headline) + .padding(.vertical, 5) + + /// Picker for choosing a project state + Picker("Select a state: ", selection: $selection) { + ForEach(states, id: \.self) { + Text($0) } - .pickerStyle(SegmentedPickerStyle()) - - HStack { - Text("Selected state: ") - + - Text("\(selection)") - .bold() - - } - .padding(.vertical, 10) + } + .pickerStyle(SegmentedPickerStyle()) + + HStack { + Text("Selected state: ") + + + Text("\(selection)") + .bold() + + } + .padding(.vertical, 10) - Spacer() + Spacer() + } + .navigationTitle(Text("Status")) + .overlay(alignment: .bottom) { + /// Adds the selection and adds filter to filter array + Button(action: { + selection = selection + filterArr.append("status") + }) { + Text("Bruk") + .frame(width: 300, height: 50, alignment: .center) } - .navigationTitle(Text("Status")) - .overlay(alignment: .bottom) { - Button(action: { - selection = selection - filterArr.append("status") - }) { - Text("Bruk") - .frame(width: 300, height: 50, alignment: .center) - } - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(10) - .padding(.bottom, 50) + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + .padding(.bottom, 50) } } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift index 7a994dbbb9e31f489d313f4bd8acd0b6e332c99f..4b4b982501a8c8459f09f2f0c0c6f5b00af9d744 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift @@ -7,13 +7,23 @@ import SwiftUI +/// **HistoryOfScaffolding** +/// A View for showing the history of a scaffolding type for a project +/// As of now, it is hard coded due to missing implementation in the API, however, the design and concept is the same. struct HistoryOfScaffolding: View { + /// All projects var projects: [Project] + + /// Darkmode or lightmode activated? @Environment(\.colorScheme) var colorScheme + /// Scaffolding type var scaffolding: Scaffolding + + /// Transfere scaffolding Modal View showing @Binding var isShowingSheet: Bool + /// Used to show dates in history let dateNow = Date() var body: some View { @@ -21,12 +31,14 @@ struct HistoryOfScaffolding: View { VStack (alignment: .leading){ HStack { VStack { + /// Each "blob" with history info RoundedRectangle(cornerRadius: 8, style: .continuous) .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) .frame(width: UIScreen.screenWidth / 2.3 , height: 60) .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: -2, y: 2) .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: -10, y: 10) .overlay(VStack { + /// Preview data for scaffolding on the project on a given date Text(Date(), style: .date) .foregroundColor(Color.gray) HStack { @@ -38,6 +50,7 @@ struct HistoryOfScaffolding: View { .font(.system(size: 10)) } VStack { + /// Changes color of registered depending on how close it is to be equal to expected amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) Text("REGISTRERT") .foregroundColor(.gray) @@ -55,6 +68,7 @@ struct HistoryOfScaffolding: View { .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: -2, y: 2) .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: -10, y: 10) .overlay(VStack { + /// Sets date to go back in time for each history "blob" Text(Calendar.current.date(byAdding: .day, value: -2, to: dateNow)!, style: .date) .foregroundColor(Color.gray) HStack { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift index f650d4302c3d632992557ffc6209373795b64b01..30cab034ff95435faea6c1cf5ffbfa3507ae64c2 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift @@ -7,45 +7,15 @@ import SwiftUI import UIKit -/* -struct BookMark: Identifiable { - let id = UUID() - let name: String - let icon: String - var items: [BookMark]? -} - -struct ProjectView1: View { - let items: [BookMark] = [.example1, .example2, .example3] - var body: some View { - VStack { - Section(header: Text("Second List")) { - List(items, children: \.items) { row in - Image(systemName: row.icon) - Text(row.name) - } - } - } - } -} - - -extension BookMark { - static let apple = BookMark(name: "Apple", icon: "1.circle") - static let bbc = BookMark(name: "BBC", icon: "square.and.pencil") - static let swift = BookMark(name: "Swfit", icon: "bolt.fill") - static let twitter = BookMark(name: "Twitter", icon: "mic") - - static let example1 = BookMark(name: "Favorites", icon: "star", items: [BookMark.apple, BookMark.bbc, BookMark.swift, BookMark.twitter]) - static let example2 = BookMark(name: "Recent", icon: "timer", items: [BookMark.apple, BookMark.bbc, BookMark.swift, BookMark.twitter]) - static let example3 = BookMark(name: "Recommended", icon: "hand.thumbsup", items: [BookMark.apple, BookMark.bbc, BookMark.swift, BookMark.twitter]) -}*/ struct ProjectInfoDetailedView: View { + /// Selected project var project: Project + /// Darkmode or lightmode? @Environment(\.colorScheme) var colorScheme + /// Predefined to be easily maintianed and changed let duration = "VARIGHET" let customer = "KUNDE" let amountScaff = "MENGDE" @@ -59,150 +29,146 @@ struct ProjectInfoDetailedView: View { var body: some View { VStack { VStack(alignment: .leading) { - //Text(projectInfoTitle) - //.font(.title).bold() - //.padding(.bottom, 5) - - VStack(alignment: .leading) { - VStack { - VStack { - Image(systemName: "square.text.square") - .resizable() - .frame(width: 30, height: 30) - .foregroundColor(.blue) - - Text("Prosjekt info") - .font(Font.system(size: 20).bold()) - .padding(.bottom, 2) - - Text("Nedenfor finner du informasjon om dette prosjektet.") - .font(.caption) - .foregroundColor(Color.gray) - .padding(.bottom, 5) - } - - VStack { - Text("\(project.customer.name)") - .font(.body) - - Text(customer) - .foregroundColor(.gray) - .font(.system(size: 15)) - } - .padding(.bottom, 5) - - VStack(alignment: .leading) { - VStack { - Text("\(project.period.startDate) - \(project.period.endDate)") - .font(.body) - - Text(duration) - .foregroundColor(.gray) - .font(.system(size: 15)) - } - } - .padding(.bottom, 5) - - VStack { - HStack { - Text("\(project.size) m") - + Text("2") - .baselineOffset(6) - .font(.system(size: 12)) - } - .font(.body) - - Text(scaffoldingSize) - .foregroundColor(.gray) - .font(.system(size: 15)) - .padding(.bottom, 5) - } - - VStack { - Text("\(project.state)") - .font(.body) - - Text(state) - .foregroundColor(.gray) - .font(.system(size: 15)) - } - } - } - .padding() - .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) - .contentShape(RoundedRectangle(cornerRadius: 5)) - .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) - .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) - .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) - + /// Project info card + VStack(alignment: .leading) { VStack { VStack { - Image(systemName: "person.circle") + Image(systemName: "square.text.square") .resizable() .frame(width: 30, height: 30) .foregroundColor(.blue) - Text("Kontakt info") + Text("Prosjekt info") .font(Font.system(size: 20).bold()) .padding(.bottom, 2) - Text("Nedenfor finner du kontaktinformasjonen til kunden.") + Text("Nedenfor finner du informasjon om dette prosjektet.") .font(.caption) .foregroundColor(Color.gray) .padding(.bottom, 5) } - + VStack { Text("\(project.customer.name)") - .font(.body) - - Text(contactPerson) + .font(.body) + + Text(customer) .foregroundColor(.gray) .font(.system(size: 15)) } .padding(.bottom, 5) - - VStack { - Text("\(project.address.street), \(project.address.zipcode) \(project.address.municipality)") + + VStack(alignment: .leading) { + VStack { + Text("\(project.period.startDate) - \(project.period.endDate)") .font(.body) - - Text(address) - .foregroundColor(.gray) - .font(.system(size: 15)) + + Text(duration) + .foregroundColor(.gray) + .font(.system(size: 15)) + } } .padding(.bottom, 5) - + VStack { - Text("\(project.customer.number)") - .font(.body) - - Text(phoneNumber) + HStack { + Text("\(project.size) m") + + Text("2") + .baselineOffset(6) + .font(.system(size: 12)) + } + .font(.body) + + Text(scaffoldingSize) .foregroundColor(.gray) .font(.system(size: 15)) + .padding(.bottom, 5) } - .padding(.bottom, 5) - + VStack { - Text("\(project.customer.email)") - .font(.body) + Text("\(project.state)") + .font(.body) - Text(email) + Text(state) .foregroundColor(.gray) .font(.system(size: 15)) } } - .padding() - .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) - .contentShape(RoundedRectangle(cornerRadius: 5)) - .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) - .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) - .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) } + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + + /// Contact info card + VStack { + VStack { + Image(systemName: "person.circle") + .resizable() + .frame(width: 30, height: 30) + .foregroundColor(.blue) + + Text("Kontakt info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du kontaktinformasjonen til kunden.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + Text("\(project.customer.name)") + .font(.body) + + Text(contactPerson) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(project.address.street), \(project.address.zipcode) \(project.address.municipality)") + .font(.body) + + Text(address) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(project.customer.number)") + .font(.body) + + Text(phoneNumber) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(project.customer.email)") + .font(.body) + + Text(email) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + } + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) } - .padding(.horizontal, 20) } - //ProjectView1() - + .padding(.horizontal, 20) + } } /* diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift index 434aa17a1eeaec24d499db7ce1e340e2bc06ec80..da4da0f60dbee413d612eef8b6c01e739f01d427 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift @@ -8,14 +8,25 @@ import SwiftUI import MapKit +/// **ProjectInfoView** +/// The View responsible for displaying the project info and scaffolding info Views struct ProjectInfoView: View { + /// Darkmode or light mode? @Environment(\.colorScheme) var colorScheme + + /// Transfere scaffolding Modal View showing? @State private var isShowingSheet = false - + + /// All projects var projects: [Project] + + /// Specific project var project: Project - let sizeSelections = ["Stillas", "Prosjekt Info"] + /// The two views available + let siteSelections = ["Stillas", "Prosjekt Info"] + + /// Initialize selection @State var selection: String = "Prosjekt Info" var body: some View { @@ -31,7 +42,7 @@ struct ProjectInfoView: View { VStack { Picker("Select a state: ", selection: $selection) { - ForEach(sizeSelections, id: \.self) { + ForEach(siteSelections, id: \.self) { Text($0) } } @@ -43,7 +54,6 @@ struct ProjectInfoView: View { switch selection { case "Prosjekt Info": VStack { - //ProjectInfoDetailedView(project: project) ProjectInfoDetailedView(project: project) } case "Stillas": diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift index 0aa0034b070db6d8acd4abaef8e5f6952e8730bf..954324068013f0def84efe2cf85702df7797c97f 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift @@ -7,13 +7,23 @@ import SwiftUI +/// **ScaffoldingDetailedView** +/// The detailed view of each scaffolding type +/// Snowes history of the type as well as a button which redirects to transfering of scaffolding struct ScaffoldingDetailedView: View { + + /// All projects used in transfere scaffolding var projects: [Project] + + /// The scaffolding type var scaffolding: Scaffolding + + /// Transfere scaffolding Modal View is showing @Binding var isShowingSheet: Bool var body: some View { VStack { + /// History of scaffolding unit type for project and transfere scaffolding button HistoryOfScaffolding(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) TransfereScaffoldingButton(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift index 30c1fd850100aeb7aaaefbe2c13345f88aaf0181..e782b36c4ee19fc1d3ad636792b7ed3603b18ef8 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift @@ -7,12 +7,18 @@ import SwiftUI +/// **ScaffoldingItem** +/// The preview used for the scaffolding buttons with scaffolding preview data struct ScaffoldingItem: View { + /// Checks if the device has light- or dark mode activated @Environment(\.colorScheme) var colorScheme + + /// The scaffolding unit var scaffolding: Scaffolding var body: some View { VStack { + /// Preview data for the button in use Text("\(scaffolding.type)".capitalizingFirstLetter()).font(.title2) .lineLimit(1) @@ -48,6 +54,12 @@ struct ScaffoldingItem: View { } } + +/// A method for checking the registered scaffolding up against the expected scaffolding and assigning color to the result text depending on how close they are to be equal to eachother +/// - Parameters: +/// - expected: The expected amount of the scaffolding type for the project +/// - registered: The registered amount of the scaffolding type for the project at a given time +/// - Returns: The text of registered units with either red, yellow, green or purple color depending on count func amountOfScaffoldingRegistered(expected: Int, registered: Int) -> Text { if (registered >= Int(Double(expected) * 0.95) && registered <= Int(Double(expected))) { return Text(String(format: "%d", registered)).foregroundColor(Color.green) @@ -64,8 +76,13 @@ func amountOfScaffoldingRegistered(expected: Int, registered: Int) -> Text { } } +/// An extension to the String struct which adds a function for capitalizing the first letter in a word. +/// This was taken in use from the following URL: /// https://www.hackingwithswift.com/example-code/strings/how-to-capitalize-the-first-letter-of-a-string extension String { + + /// Capitalizes the first letter of a word + /// - Returns: The word with the capitalized first letter func capitalizingFirstLetter() -> String { return prefix(1).capitalized + dropFirst() } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift index 645455c15cf4bfb0db968d8eed7522ad9924f74d..cd420009dcb87a83e6346d9fafabd91b22fd3c63 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift @@ -7,15 +7,25 @@ import SwiftUI +/// **ScaffoldingItems** +/// A grid containing all the scaffolding items as buttons struct ScaffoldingItems: View { + /// Projects needed for transfering scaffolding var projects: [Project] + + /// A two-column grid layout which is flexible var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible())] + + /// All scaffolding items var scaffolding: [Scaffolding] + + /// Modal View of transfere scaffolding @Binding var isShowingSheet: Bool var body: some View { ScrollView (.vertical) { LazyVGrid (columns: gridItemLayout, spacing: 10) { + /// For each scaffolding type on the project, add scaffolding button ForEach(scaffolding, id: \.type) { scaffolding in NavigationLink(destination: ScaffoldingDetailedView(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet), label: { ScaffoldingItem(scaffolding: scaffolding) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift index 2b34a14574884da5f894481e657d4e35b8827ad3..9f4fa8b709ebb451dd4c1c6e2ba7f6d9a68f2cbd 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift @@ -7,9 +7,15 @@ import SwiftUI +/// **ScaffoldingView** +/// The view responsible for showing scaffolding units struct ScaffoldingView: View { var projects: [Project] + + /// All scaffolding units var scaffolding: [Scaffolding] + + /// Scaffolding item Modal View @State var isShowingSheet: Bool = false var body: some View { VStack { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift index 559f9be29938b7bb57a335b465ab0c7236f9aed9..813f99cd8af7cdbd6c1ac9af27d2459c6cc23e40 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift @@ -8,26 +8,30 @@ import SwiftUI import Foundation +/// **TrandsfereScaffolding** +/// Background view of TransfereScaffolding struct TransfereScaffolding: View { + /// All projects used for transfering var projects: [Project] + + /// Lightmode or darkmode? @Environment(\.colorScheme) var colorScheme + /// Scaffolding type var scaffolding: Scaffolding - @Binding var isShowingSheet: Bool - @State private var quantity: Int = 1 - @State private var name: String = "Tim" - @State private var projectFrom: String = "" - @State private var projectTo: String = "" - + /// Transfere Modal View is showing + @Binding var isShowingSheet: Bool var body: some View { VStack { + /// Transfere scaffolding view TransfereScaffoldingView(isShowingSheet: $isShowingSheet, projects: projects, scaffolding: scaffolding) .navigationTitle(Text("Overfør \(scaffolding.type)")) } } + /// Modal view dismissed func didDismiss() { // Handle the dismissing action. } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift index d1bee14869f04908f41bac7de72ee502c3fe798c..1a061eb4865d103182abf2b0dfecee58bd7e86ef 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift @@ -7,13 +7,23 @@ import SwiftUI +/// **TransfereScaffoldingButton** +/// Button for activating of transfere scaffolding Modal View struct TransfereScaffoldingButton: View { + /// All projects var projects: [Project] + + /// Darkmode or lightmode activated? @Environment(\.colorScheme) var colorScheme + + /// Specific scaffolding type var scaffolding: Scaffolding + + /// Transfere scaffolding Modal View is showing @Binding var isShowingSheet: Bool var body: some View { + /// Button for opening transfere scaffolding Modal View Button { isShowingSheet.toggle() } label: { @@ -34,10 +44,12 @@ struct TransfereScaffoldingButton: View { .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) .sheet(isPresented: $isShowingSheet, onDismiss: didDismiss) { + /// Transfere scaffolding Modal View TransfereScaffolding(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) } } + /// Modal view got dismissed func didDismiss() { // Handle the dismissing action. } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift index dbb61b71b7fabd95bb7fad3f15f1460a76c5b848..49d8aff8bbcf741a3f6a90443c1df936f8d32df7 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift @@ -8,44 +8,63 @@ import SwiftUI import Combine +/// **TransfereScaffoldingView** +/// The View for transfering scaffolding struct TransfereScaffoldingView: View { + /// Input field enum Field: Int, CaseIterable { case input } + + /// Is Modal View active @Binding var isShowingSheet: Bool + /// Field is selected --> Focus @FocusState var focusedField: Field? + /// All projects var projects: [Project] + + /// Scaffolding type var scaffolding: Scaffolding + + /// Value of project picker selection @State private var pickerSelectionFrom: String = "" @State private var pickerSelectionTo: String = "" + /// Checks if "from project" is not selected yet @State private var pickerSelectionFromNotPicked: Bool = true + /// Search for projects @State private var searchTerm: String = "" + /// Is amount of scaffolding empty? @State private var empty = false + /// The number of scaffolding (as string because of textfield) @State var anumber: String = "" + /// Template numbers to be picked from var commonNumbers: [String] = ["1", "5", "10", "25", "50"] @State var selectedIndex: Int = -1 - @State private var confirmationMessage = "" @State private var confirmationTitle = "" @State private var showingConfirmation = false @State private var transfereMessage = "" @State private var transfereConfirmation: Bool = false + + /// ProjectID's @State var pickedFromID: Int = 0 @State var pickedToID: Int = 0 @State var tempIntFrom: Int = 0 @State var tempIntTo: Int = 0 + /// Checks the expected count of the selected project @State var tem: Int = 0 + /// The filtered projects var filteredProjects: [Project] { projects.filter { searchTerm.isEmpty ? true : $0.projectName.lowercased().contains(searchTerm.lowercased()) @@ -56,10 +75,12 @@ struct TransfereScaffoldingView: View { NavigationView { Form { Section(header: Text("Fra prosjekt")) { + /// Picker for the project to transfere scaffolding from Picker(selection: $pickerSelectionFrom, label: Text("Fra prosjekt")) { SearchBar(text: $searchTerm, placeholder: "Søk etter prosjekt") .onAppear { searchTerm = "" + /// Reset number of scaffolding anumber.removeAll() } .onChange(of: pickerSelectionFrom) { pick in @@ -67,6 +88,7 @@ struct TransfereScaffoldingView: View { pickedFromID = projects[tempIntFrom].projectID pickerSelectionFromNotPicked = false } + /// List all projects ForEach(filteredProjects, id: \.projectID) { project in Text("\(project.projectName)").tag("\(project.projectName)") } @@ -79,6 +101,7 @@ struct TransfereScaffoldingView: View { } Section(header: Text("Til prosjekt")) { + /// Picker for the project to transfere scaffolding to Picker(selection: $pickerSelectionTo, label: Text("Til prosjekt")) { SearchBar(text: $searchTerm, placeholder: "Søk etter prosjekt") .onAppear { @@ -88,6 +111,7 @@ struct TransfereScaffoldingView: View { tempIntTo = projects.firstIndex(where: { $0.projectName == pick })! pickedToID = projects[tempIntTo].projectID } + /// List all projects ForEach(filteredProjects, id: \.projectID) { project in Text("\(project.projectName)").tag("\(project.projectName)") } @@ -96,62 +120,65 @@ struct TransfereScaffoldingView: View { Section (header: Text("Antall \(scaffolding.type)")){ VStack(alignment: .leading) { - Text("Forhåndsdefinert mengde") - Picker("Pick a number", selection: $anumber) { - ForEach(commonNumbers, id: \.self) { aNumber in - Image(systemName: "\(aNumber).circle.fill") - } + Text("Forhåndsdefinert mengde") + /// Picker for the predefined scaffolding amount + Picker("Pick a number", selection: $anumber) { + ForEach(commonNumbers, id: \.self) { aNumber in + Image(systemName: "\(aNumber).circle.fill") } - .pickerStyle(SegmentedPickerStyle()) } - .padding() + .pickerStyle(SegmentedPickerStyle()) + } + .padding() - HStack { - Text("Manuell innfylling") - TextField("Input", text: $anumber) - .disabled(pickerSelectionFromNotPicked) - //.modifier(ClearButton(text: $anumber)) - .onChange(of: anumber) { - tem = (projects[tempIntFrom].scaffolding![projects[tempIntFrom].scaffolding!.firstIndex(where: { $0.type == scaffolding.type}) ?? 0].quantity.expected) - - if $0.isEmpty || Int($0) ?? -1 > tem { - empty = true - } else { - empty = false - } + HStack { + Text("Manuell innfylling") + TextField("Input", text: $anumber) + .disabled(pickerSelectionFromNotPicked) + //.modifier(ClearButton(text: $anumber)) + .onChange(of: anumber) { + tem = (projects[tempIntFrom].scaffolding![projects[tempIntFrom].scaffolding!.firstIndex(where: { $0.type == scaffolding.type}) ?? 0].quantity.expected) + + if $0.isEmpty || Int($0) ?? -1 > tem { + empty = true + } else { + empty = false } - .textFieldStyle(TextFieldEmpty(empty: $empty)) - .keyboardType(.numberPad) - .focused($focusedField, equals: .input) - /// https://stackoverflow.com/questions/58733003/how-to-create-textfield-that-only-accepts-numbers - .onReceive(Just(anumber)) { newValue in - let filtered = newValue.filter { "0123456789".contains($0) } - if filtered != newValue { - self.anumber = filtered - } - } - .toolbar { - ToolbarItem(placement: .keyboard) { - Button("Done") { - focusedField = nil - } + } + .textFieldStyle(TextFieldEmpty(empty: $empty)) + .keyboardType(.numberPad) + .focused($focusedField, equals: .input) + /// Code taken from: + /// https://stackoverflow.com/questions/58733003/how-to-create-textfield-that-only-accepts-numbers + /// to make sure input is of type Int and update picker + .onReceive(Just(anumber)) { newValue in + let filtered = newValue.filter { "0123456789".contains($0) } + if filtered != newValue { + self.anumber = filtered + } + } + .toolbar { + ToolbarItem(placement: .keyboard) { + Button("Done") { + focusedField = nil } } + } } .padding() } } .overlay(alignment: .bottom) { Spacer() - + /// Button to start transaction for transfering scaffolding Button(action: { Task { if !empty { + /// Starts transfere process if alert is confirmed transfereMessage = "Overfør \(Int(anumber)!)x \(scaffolding.type) fra \(pickerSelectionFrom) til \(pickerSelectionTo)?" transfereConfirmation = true } } - }) { Text("Bruk") .frame(width: 300, height: 50, alignment: .center) @@ -164,6 +191,7 @@ struct TransfereScaffoldingView: View { .ignoresSafeArea(.keyboard) .navigationTitle(Text("Overfør \(scaffolding.type)")) .alert(isPresented: $showingConfirmation) { + /// Transfere was successful alert Alert( title: Text(confirmationTitle), message: Text(confirmationMessage), @@ -175,6 +203,7 @@ struct TransfereScaffoldingView: View { } } .alert(isPresented: $transfereConfirmation) { + /// Checks if the user wants to proceed. If yes, execute request to the API Alert( title: Text("Er du sikker på at du vil fortsette?"), message: Text(transfereMessage), @@ -182,6 +211,7 @@ struct TransfereScaffoldingView: View { transfereConfirmation = false Task { if !empty { + /// Starts request to transfere scaffoling await transfereScaffoldingUnit(pickedFromID: Int(exactly: pickedFromID)!, pickedToID: Int(exactly: pickedToID)!, transfereAmount: Int(anumber)!) } } @@ -194,42 +224,50 @@ struct TransfereScaffoldingView: View { } } + /// Establishes connection to the API and sendts a PUT request to the API with the scaffolding amount from which project to which project. + /// The code is partially taken and inspired from: /// https://www.appsdeveloperblog.com/http-post-request-example-in-swift/ + /// - Parameters: + /// - pickedFromID: The ID of the project to transfere scaffolding from + /// - pickedToID: The ID of the project to transfere scaffolding to + /// - transfereAmount: The amount of scaffolding to transfere func transfereScaffoldingUnit(pickedFromID: Int, pickedToID: Int, transfereAmount: Int) async { - let url = URL(string: "http://10.212.138.205:8080/stillastracking/v1/api/project/scaffolding") guard let requestUrl = url else { fatalError() } // Prepare URL Request Object var request = URLRequest(url: requestUrl) request.httpMethod = "PUT" - + /// Sets the body to be of type Scaff let body : Scaff = Scaff(scaffold: [Move(type: "\(scaffolding.type.capitalizingFirstLetter())", quantity: transfereAmount)], toProjectID: pickedToID, fromProjectID: pickedFromID) - print(body) - + /// Tries to encode body guard let jsonData = try? JSONEncoder().encode(body) else { print("Failed to encode order") return } + /// Makes a jsonString of the encoded data let jsonString = String(data: jsonData, encoding: .utf8)! + /// Creates the request body with the jsonStrings data request.httpBody = jsonString.data(using: String.Encoding.utf8); - // Perform HTTP Request + + /// Perform HTTP Request let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - // Check for Error + /// Check for Error if let error = error { print("Error took place \(error)") return } - // Convert HTTP Response Data to a String + /// Convert HTTP Response Data to a String if let data = data, let dataString = String(data: data, encoding: .utf8) { print("Response data string:\n \(dataString)") } + /// Checks the response's statuscode and displays the alert based on the code if let response = response as? HTTPURLResponse { if response.statusCode == 200 { confirmationTitle = "Suksess!" diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift index 58e85bbcba23d2a8e173d11113d47f86acc2a45f..79aa8f0eb0972de501973687cb9944aa554c69fb 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift @@ -7,7 +7,8 @@ import SwiftUI import UIKit - + +/// Enum for the different filtertypes enum FilterType { case none, period, @@ -23,13 +24,16 @@ enum FilterType { county } +/// **ProjectRow** +/// The project lists information preview struct ProjectRow: View { + /// The relevant project var project: Project var body: some View { HStack { VStack(alignment: .leading) { - Text(project.projectName).font(.headline).bold().italic()//.font(.headline) + Text(project.projectName).font(.headline).bold().italic() Text(project.period.startDate + " - " + project.period.endDate).font(.subheadline).foregroundColor(.gray) } Spacer() @@ -39,21 +43,34 @@ struct ProjectRow: View { } } +/// **ProjectListView** +/// The project list struct ProjectListView: View { + /// Initializes an instance of ProjectData to call the loadData method @ObservedObject var projectData: ProjectData = ProjectData() + /// Data of searchQuery @State var searchQuery = "" + @State var hasFetchedData = false + + /// Initializes new Project instance to get project details @State var projects = [Project]() + + /// Filter- or add project Modal Views active @State private var showFilterModalView: Bool = false @State private var showAddProjectModalView: Bool = false + /// Initializes sort on size filter page @State var sizeSortType: String = "Mellom" + + /// Initializes filter to be no filter @State var filter: FilterType = .none @State var filterArr: [String] = [] @State var filterArrArea: [String] = [] // TODO: REMOVE? + /// Initialized values passed into filter @State var projectStartDate = Date.distantPast @State var projectEndDate = Date.distantFuture @State var projectSize = 99999 @@ -61,19 +78,25 @@ struct ProjectListView: View { @State var maxProjectSize = 1000 @State var projectState = "Active" @State var projectCounty = "Innlandet" + + /// Data is loading @State private var isLoading = true + var body: some View { VStack { NavigationView { ZStack { + /// Data is loading --> show ProgressView if projectData.isLoading { Spacer().frame(height:100) ProgressView("Laster inn...") .progressViewStyle(CircularProgressViewStyle(tint: .gray)) .scaleEffect(x: 1.2, y: 1.2, anchor: .center) } else { + /// Displays all projects Form { Section(header: Text("Alle Prosjekter")) { + /// List with all projects, filtered projects or searched for projects List(searchResults, id: \.projectID) { project in NavigationLink(destination: ProjectInfoView(projects: projects, project: project), label: { ProjectRow(project: project) } @@ -84,6 +107,7 @@ struct ProjectListView: View { } .listStyle(.grouped) .toolbar { + /// Button for adding filter ToolbarItemGroup(placement: .navigationBarLeading) { Button(action: { print("Filter tapped!") @@ -93,6 +117,7 @@ struct ProjectListView: View { } } + /// Button for adding project ToolbarItemGroup(placement: .navigationBarTrailing) { Button(action: { print("Add project tapped!") @@ -106,12 +131,14 @@ struct ProjectListView: View { } } .task { + /// On first time opening --> Load in project data await projectData.loadData { (projects) in self.projects = projects } } .sheet(isPresented: $showFilterModalView, onDismiss: didDismiss) { + /// Modal View for filtering projects FilterView(selStartDateBind: $projectStartDate, selEndDateBind: $projectEndDate, projectArea: $projectCounty, projectSize: $projectSize, projectStatus: $projectState, minProjectSize: $minProjectSize, maxProjectSize: $maxProjectSize, sizeSortType: $sizeSortType, filterArr: $filterArr, filterArrArea: $filterArrArea) .onChange(of: filterArr) { filterVal in if filterArr.contains("period") { @@ -138,6 +165,7 @@ struct ProjectListView: View { .navigationViewStyle(.stack) .searchable(text: $searchQuery, placement: .navigationBarDrawer(displayMode: .always)) .refreshable { + /// Projectdata is fetches again from the API await projectData.loadData { (projects) in self.projects = projects } @@ -145,6 +173,8 @@ struct ProjectListView: View { } } .sheet(isPresented: $showAddProjectModalView, onDismiss: didDismiss){ + /// Modal View for adding project + /// Not implemented yet Text("501") .font(.system(size: 40).bold()) Text("Not Implemented") @@ -152,6 +182,7 @@ struct ProjectListView: View { } } + /// Returns all the projects sorted and with the filter if applied var searchResults: [Project] { if searchQuery.isEmpty { return filteredProjects.sorted { $0.projectName < $1.projectName } @@ -160,10 +191,13 @@ struct ProjectListView: View { } } + + /// Modal View dismissed func didDismiss() { // Handle the dismissing action. } + /// The projects filtered based on the passed in filter var filteredProjects: [Project] { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "dd/MM/yyyy" @@ -172,19 +206,14 @@ struct ProjectListView: View { case .none: return projects case .period: - //return projects.filter { $0.period.startDate > projectStartDate && $0.period.endDate < projectEndDate } return projects.filter { dateFormatter.date(from: $0.period.startDate)! >= projectStartDate && dateFormatter.date(from: $0.period.endDate)! <= projectEndDate } case .startBeforePeriod: - //return projects.filter { $0.period.startDate < projectStartDate } return projects.filter { dateFormatter.date(from: $0.period.startDate)! <= projectStartDate } case .startAfterPeriod: - //return projects.filter { $0.period.startDate > projectStartDate } return projects.filter { dateFormatter.date(from: $0.period.startDate)! >= projectStartDate } case .endBeforePeriod: - //return projects.filter { $0.period.endDate < projectEndDate } return projects.filter { dateFormatter.date(from: $0.period.endDate)! <= projectEndDate } case .endAfterPeriod: - //return projects.filter { $0.period.endDate > projectEndDate } return projects.filter { dateFormatter.date(from: $0.period.endDate)! >= projectEndDate } case .sizeBetween: return projects.filter { $0.size >= Int(minProjectSize) && $0.size <= Int(maxProjectSize)} diff --git a/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift b/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift index 19fa684f37446bf866c303ec6cf78e63947d883b..93bc73635cab038fe37be8daae2a9c3e23e150da 100644 --- a/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift +++ b/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift @@ -7,16 +7,10 @@ import SwiftUI import Firebase -/* -@main -struct stillasMobileApplicationApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -}*/ + +/// **AppDelegate** +/// Initializes the application with FirebaseApp class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { FirebaseApp.configure() @@ -25,6 +19,9 @@ class AppDelegate: NSObject, UIApplicationDelegate { } @main +/// **stillasMobileApplication** +/// Create an app by declaring a structure that conforms to the App protocol. +/// Assigns the AppDelegate. struct stillasMobileApplication: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate