diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/Space HUD.xcodeproj/project.pbxproj b/Space HUD.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ec340d8 --- /dev/null +++ b/Space HUD.xcodeproj/project.pbxproj @@ -0,0 +1,522 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 76411A6B2928647E0097C8C3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A6A2928647E0097C8C3 /* AppDelegate.swift */; }; + 76411A6F2928647F0097C8C3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 76411A6E2928647F0097C8C3 /* Assets.xcassets */; }; + 76411A722928647F0097C8C3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 76411A702928647F0097C8C3 /* Main.storyboard */; }; + 76411A7D2928659D0097C8C3 /* DefaultsEtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A7C2928659D0097C8C3 /* DefaultsEtension.swift */; }; + 76411A7F292865C70097C8C3 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A7E292865C70097C8C3 /* Constants.swift */; }; + 76411A81292865E90097C8C3 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A80292865E90097C8C3 /* ColorExtensions.swift */; }; + 76411A83292866050097C8C3 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A82292866050097C8C3 /* ViewExtensions.swift */; }; + 76411A86292866270097C8C3 /* Dtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A85292866270097C8C3 /* Dtos.swift */; }; + 76411A88292866320097C8C3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A87292866320097C8C3 /* Client.swift */; }; + 76411A8A292866480097C8C3 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A89292866480097C8C3 /* ViewModel.swift */; }; + 76411A8C292866820097C8C3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A8B292866820097C8C3 /* ContentView.swift */; }; + 76411A8E292866980097C8C3 /* IssuesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A8D292866980097C8C3 /* IssuesView.swift */; }; + 76411A90292866BD0097C8C3 /* IssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A8F292866BD0097C8C3 /* IssueView.swift */; }; + 76411A92292866D30097C8C3 /* IssueStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A91292866D30097C8C3 /* IssueStatusView.swift */; }; + 76411A94292866E80097C8C3 /* CodeReviewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A93292866E80097C8C3 /* CodeReviewsView.swift */; }; + 76411A96292866FB0097C8C3 /* CodeReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A95292866FB0097C8C3 /* CodeReviewView.swift */; }; + 76411A98292867140097C8C3 /* TodosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A97292867140097C8C3 /* TodosView.swift */; }; + 76411A9A292867270097C8C3 /* TodoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A99292867270097C8C3 /* TodoView.swift */; }; + 76411A9C292867400097C8C3 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A9B292867400097C8C3 /* AboutView.swift */; }; + 76411A9E292867560097C8C3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A9D292867560097C8C3 /* SettingsView.swift */; }; + 76411AA0292867830097C8C3 /* NavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411A9F292867830097C8C3 /* NavLink.swift */; }; + 76411AA2292867AE0097C8C3 /* KeyChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411AA1292867AE0097C8C3 /* KeyChain.swift */; }; + 76411AA4292867CD0097C8C3 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76411AA3292867CD0097C8C3 /* Notifications.swift */; }; + 76411AA7292867ED0097C8C3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 76411AA6292867ED0097C8C3 /* Alamofire */; }; + 76411AAA292867FB0097C8C3 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 76411AA9292867FB0097C8C3 /* Defaults */; }; + 76411AAD292868100097C8C3 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 76411AAC292868100097C8C3 /* KeychainAccess */; }; + 76411AB02928688E0097C8C3 /* SkeletonUI in Frameworks */ = {isa = PBXBuildFile; productRef = 76411AAF2928688E0097C8C3 /* SkeletonUI */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 76411A672928647E0097C8C3 /* Space HUD.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Space HUD.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 76411A6A2928647E0097C8C3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 76411A6E2928647F0097C8C3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 76411A712928647F0097C8C3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 76411A732928647F0097C8C3 /* Space_HUD.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Space_HUD.entitlements; sourceTree = ""; }; + 76411A7C2928659D0097C8C3 /* DefaultsEtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsEtension.swift; sourceTree = ""; }; + 76411A7E292865C70097C8C3 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 76411A80292865E90097C8C3 /* ColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensions.swift; sourceTree = ""; }; + 76411A82292866050097C8C3 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = ""; }; + 76411A85292866270097C8C3 /* Dtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dtos.swift; sourceTree = ""; }; + 76411A87292866320097C8C3 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; + 76411A89292866480097C8C3 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; + 76411A8B292866820097C8C3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 76411A8D292866980097C8C3 /* IssuesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuesView.swift; sourceTree = ""; }; + 76411A8F292866BD0097C8C3 /* IssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueView.swift; sourceTree = ""; }; + 76411A91292866D30097C8C3 /* IssueStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueStatusView.swift; sourceTree = ""; }; + 76411A93292866E80097C8C3 /* CodeReviewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeReviewsView.swift; sourceTree = ""; }; + 76411A95292866FB0097C8C3 /* CodeReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeReviewView.swift; sourceTree = ""; }; + 76411A97292867140097C8C3 /* TodosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodosView.swift; sourceTree = ""; }; + 76411A99292867270097C8C3 /* TodoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoView.swift; sourceTree = ""; }; + 76411A9B292867400097C8C3 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 76411A9D292867560097C8C3 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 76411A9F292867830097C8C3 /* NavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavLink.swift; sourceTree = ""; }; + 76411AA1292867AE0097C8C3 /* KeyChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChain.swift; sourceTree = ""; }; + 76411AA3292867CD0097C8C3 /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 76411A642928647E0097C8C3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 76411AAA292867FB0097C8C3 /* Defaults in Frameworks */, + 76411AB02928688E0097C8C3 /* SkeletonUI in Frameworks */, + 76411AA7292867ED0097C8C3 /* Alamofire in Frameworks */, + 76411AAD292868100097C8C3 /* KeychainAccess in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 76411A5E2928647E0097C8C3 = { + isa = PBXGroup; + children = ( + 76411A692928647E0097C8C3 /* Space HUD */, + 76411A682928647E0097C8C3 /* Products */, + ); + sourceTree = ""; + }; + 76411A682928647E0097C8C3 /* Products */ = { + isa = PBXGroup; + children = ( + 76411A672928647E0097C8C3 /* Space HUD.app */, + ); + name = Products; + sourceTree = ""; + }; + 76411A692928647E0097C8C3 /* Space HUD */ = { + isa = PBXGroup; + children = ( + 76411A79292864E70097C8C3 /* Extensions */, + 76411A7A292865260097C8C3 /* Space */, + 76411A7B292865350097C8C3 /* Views */, + 76411A6A2928647E0097C8C3 /* AppDelegate.swift */, + 76411A6E2928647F0097C8C3 /* Assets.xcassets */, + 76411A702928647F0097C8C3 /* Main.storyboard */, + 76411A732928647F0097C8C3 /* Space_HUD.entitlements */, + 76411AA1292867AE0097C8C3 /* KeyChain.swift */, + 76411AA3292867CD0097C8C3 /* Notifications.swift */, + ); + path = "Space HUD"; + sourceTree = ""; + }; + 76411A79292864E70097C8C3 /* Extensions */ = { + isa = PBXGroup; + children = ( + 76411A7C2928659D0097C8C3 /* DefaultsEtension.swift */, + 76411A7E292865C70097C8C3 /* Constants.swift */, + 76411A80292865E90097C8C3 /* ColorExtensions.swift */, + 76411A82292866050097C8C3 /* ViewExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 76411A7A292865260097C8C3 /* Space */ = { + isa = PBXGroup; + children = ( + 76411A85292866270097C8C3 /* Dtos.swift */, + 76411A87292866320097C8C3 /* Client.swift */, + 76411A89292866480097C8C3 /* ViewModel.swift */, + ); + path = Space; + sourceTree = ""; + }; + 76411A7B292865350097C8C3 /* Views */ = { + isa = PBXGroup; + children = ( + 76411A8B292866820097C8C3 /* ContentView.swift */, + 76411A8D292866980097C8C3 /* IssuesView.swift */, + 76411A8F292866BD0097C8C3 /* IssueView.swift */, + 76411A91292866D30097C8C3 /* IssueStatusView.swift */, + 76411A93292866E80097C8C3 /* CodeReviewsView.swift */, + 76411A95292866FB0097C8C3 /* CodeReviewView.swift */, + 76411A97292867140097C8C3 /* TodosView.swift */, + 76411A99292867270097C8C3 /* TodoView.swift */, + 76411A9B292867400097C8C3 /* AboutView.swift */, + 76411A9D292867560097C8C3 /* SettingsView.swift */, + 76411A9F292867830097C8C3 /* NavLink.swift */, + ); + path = Views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 76411A662928647E0097C8C3 /* Space HUD */ = { + isa = PBXNativeTarget; + buildConfigurationList = 76411A762928647F0097C8C3 /* Build configuration list for PBXNativeTarget "Space HUD" */; + buildPhases = ( + 76411A632928647E0097C8C3 /* Sources */, + 76411A642928647E0097C8C3 /* Frameworks */, + 76411A652928647E0097C8C3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Space HUD"; + packageProductDependencies = ( + 76411AA6292867ED0097C8C3 /* Alamofire */, + 76411AA9292867FB0097C8C3 /* Defaults */, + 76411AAC292868100097C8C3 /* KeychainAccess */, + 76411AAF2928688E0097C8C3 /* SkeletonUI */, + ); + productName = "Space HUD"; + productReference = 76411A672928647E0097C8C3 /* Space HUD.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 76411A5F2928647E0097C8C3 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1410; + LastUpgradeCheck = 1410; + TargetAttributes = { + 76411A662928647E0097C8C3 = { + CreatedOnToolsVersion = 14.1; + }; + }; + }; + buildConfigurationList = 76411A622928647E0097C8C3 /* Build configuration list for PBXProject "Space HUD" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 76411A5E2928647E0097C8C3; + packageReferences = ( + 76411AA5292867ED0097C8C3 /* XCRemoteSwiftPackageReference "Alamofire" */, + 76411AA8292867FB0097C8C3 /* XCRemoteSwiftPackageReference "Defaults" */, + 76411AAB292868100097C8C3 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + 76411AAE2928688E0097C8C3 /* XCRemoteSwiftPackageReference "SkeletonUI" */, + ); + productRefGroup = 76411A682928647E0097C8C3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 76411A662928647E0097C8C3 /* Space HUD */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 76411A652928647E0097C8C3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 76411A6F2928647F0097C8C3 /* Assets.xcassets in Resources */, + 76411A722928647F0097C8C3 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 76411A632928647E0097C8C3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 76411A96292866FB0097C8C3 /* CodeReviewView.swift in Sources */, + 76411A8A292866480097C8C3 /* ViewModel.swift in Sources */, + 76411A81292865E90097C8C3 /* ColorExtensions.swift in Sources */, + 76411A9C292867400097C8C3 /* AboutView.swift in Sources */, + 76411A6B2928647E0097C8C3 /* AppDelegate.swift in Sources */, + 76411A7D2928659D0097C8C3 /* DefaultsEtension.swift in Sources */, + 76411A83292866050097C8C3 /* ViewExtensions.swift in Sources */, + 76411A92292866D30097C8C3 /* IssueStatusView.swift in Sources */, + 76411A94292866E80097C8C3 /* CodeReviewsView.swift in Sources */, + 76411A8C292866820097C8C3 /* ContentView.swift in Sources */, + 76411A7F292865C70097C8C3 /* Constants.swift in Sources */, + 76411A88292866320097C8C3 /* Client.swift in Sources */, + 76411A86292866270097C8C3 /* Dtos.swift in Sources */, + 76411A90292866BD0097C8C3 /* IssueView.swift in Sources */, + 76411AA0292867830097C8C3 /* NavLink.swift in Sources */, + 76411AA4292867CD0097C8C3 /* Notifications.swift in Sources */, + 76411A9A292867270097C8C3 /* TodoView.swift in Sources */, + 76411A98292867140097C8C3 /* TodosView.swift in Sources */, + 76411AA2292867AE0097C8C3 /* KeyChain.swift in Sources */, + 76411A8E292866980097C8C3 /* IssuesView.swift in Sources */, + 76411A9E292867560097C8C3 /* SettingsView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 76411A702928647F0097C8C3 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 76411A712928647F0097C8C3 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 76411A742928647F0097C8C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 76411A752928647F0097C8C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 76411A772928647F0097C8C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Space HUD/Space_HUD.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UV3HUS49VJ; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.pavelmakhov.Space-HUD"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 76411A782928647F0097C8C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Space HUD/Space_HUD.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UV3HUS49VJ; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.pavelmakhov.Space-HUD"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 76411A622928647E0097C8C3 /* Build configuration list for PBXProject "Space HUD" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 76411A742928647F0097C8C3 /* Debug */, + 76411A752928647F0097C8C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 76411A762928647F0097C8C3 /* Build configuration list for PBXNativeTarget "Space HUD" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 76411A772928647F0097C8C3 /* Debug */, + 76411A782928647F0097C8C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 76411AA5292867ED0097C8C3 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire"; + requirement = { + branch = master; + kind = branch; + }; + }; + 76411AA8292867FB0097C8C3 /* XCRemoteSwiftPackageReference "Defaults" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sindresorhus/Defaults"; + requirement = { + branch = main; + kind = branch; + }; + }; + 76411AAB292868100097C8C3 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess"; + requirement = { + branch = master; + kind = branch; + }; + }; + 76411AAE2928688E0097C8C3 /* XCRemoteSwiftPackageReference "SkeletonUI" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/CSolanaM/SkeletonUI"; + requirement = { + branch = develop; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 76411AA6292867ED0097C8C3 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 76411AA5292867ED0097C8C3 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + 76411AA9292867FB0097C8C3 /* Defaults */ = { + isa = XCSwiftPackageProductDependency; + package = 76411AA8292867FB0097C8C3 /* XCRemoteSwiftPackageReference "Defaults" */; + productName = Defaults; + }; + 76411AAC292868100097C8C3 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = 76411AAB292868100097C8C3 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + 76411AAF2928688E0097C8C3 /* SkeletonUI */ = { + isa = XCSwiftPackageProductDependency; + package = 76411AAE2928688E0097C8C3 /* XCRemoteSwiftPackageReference "SkeletonUI" */; + productName = SkeletonUI; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 76411A5F2928647E0097C8C3 /* Project object */; +} diff --git a/Space HUD.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Space HUD.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Space HUD.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Space HUD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Space HUD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Space HUD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Space HUD.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Space HUD.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..ef9c244 --- /dev/null +++ b/Space HUD.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,50 @@ +{ + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire", + "state" : { + "branch" : "master", + "revision" : "7884a65c6c251088c95eae70acd9adfd08fa7c68" + } + }, + { + "identity" : "defaults", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/Defaults", + "state" : { + "branch" : "main", + "revision" : "35943225a081e26b6ef5126312198e884512cbb6" + } + }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess", + "state" : { + "branch" : "master", + "revision" : "6299daec1d74be12164fec090faf9ed14d0da9d6" + } + }, + { + "identity" : "skeletonui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/CSolanaM/SkeletonUI", + "state" : { + "branch" : "develop", + "revision" : "98225dbb0459477dfeac063e1c7d0750102325d1" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "f29e2014f6230cf7d5138fc899da51c7f513d467", + "version" : "1.10.0" + } + } + ], + "version" : 2 +} diff --git a/Space HUD.xcodeproj/project.xcworkspace/xcuserdata/caseyjones.xcuserdatad/UserInterfaceState.xcuserstate b/Space HUD.xcodeproj/project.xcworkspace/xcuserdata/caseyjones.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..5d3c0c2 Binary files /dev/null and b/Space HUD.xcodeproj/project.xcworkspace/xcuserdata/caseyjones.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Space HUD.xcodeproj/xcuserdata/caseyjones.xcuserdatad/xcschemes/xcschememanagement.plist b/Space HUD.xcodeproj/xcuserdata/caseyjones.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..34420fb --- /dev/null +++ b/Space HUD.xcodeproj/xcuserdata/caseyjones.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Space HUD.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Space HUD/AppDelegate.swift b/Space HUD/AppDelegate.swift new file mode 100644 index 0000000..0b8700d --- /dev/null +++ b/Space HUD/AppDelegate.swift @@ -0,0 +1,73 @@ +// +// AppDelegate.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Cocoa +import SwiftUI + +@main +class AppDelegate: NSObject, NSApplicationDelegate { + + var popover: NSPopover! + var statusBarItem: NSStatusItem! + @ObservedObject var model = Model() + let client = SpaceClient() + var contentView: ContentView? + + func applicationDidFinishLaunching(_ aNotification: Notification) { + + contentView = ContentView(model: self.model) + + let popover = NSPopover() + popover.contentSize = NSSize(width: 600, height: 400) + popover.behavior = .transient + popover.contentViewController = NSHostingController(rootView: contentView) + self.popover = popover + + self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength)) + + if let button = self.statusBarItem.button { + let logo = NSImage(named: "space-logo") + logo?.size = NSSize(width: 18, height: 18) + button.image = NSImage(named: "space-logo") + button.action = #selector(togglePopover(_:)) + } + + client.getIssueStatuses{ resp in + Constants.issueStatuses = resp + } + + + NSApp.setActivationPolicy(.accessory) + } + + @objc func togglePopover(_ sender: AnyObject?) { + if let button = self.statusBarItem.button { + if self.popover.isShown { + self.popover.performClose(sender) + } else { + self.model.refresh(); + self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) + } + } + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } + + @objc + func quit() { + NSLog("User click Quit") + NSApplication.shared.terminate(self) + } + +} + diff --git a/Space HUD/Assets.xcassets/AccentColor.colorset/Contents.json b/Space HUD/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Space HUD/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/Contents.json b/Space HUD/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..5a7f96f --- /dev/null +++ b/Space HUD/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "spacebar-16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "spacebar-32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "spacebar-33.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "spacebar-64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "spacebar-128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "spacebar-256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "spacebar-257.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "spacebar-512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "spacebar-513.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "spacebar-1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-1024.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-1024.png new file mode 100644 index 0000000..81deb2a Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-1024.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-128.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-128.png new file mode 100644 index 0000000..22eaf01 Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-128.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-16.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-16.png new file mode 100644 index 0000000..3ecf8fb Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-16.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-256.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-256.png new file mode 100644 index 0000000..960dcd9 Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-256.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-257.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-257.png new file mode 100644 index 0000000..960dcd9 Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-257.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-32.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-32.png new file mode 100644 index 0000000..7909059 Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-32.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-33.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-33.png new file mode 100644 index 0000000..7909059 Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-33.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-512.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-512.png new file mode 100644 index 0000000..5f96d9a Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-512.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-513.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-513.png new file mode 100644 index 0000000..5f96d9a Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-513.png differ diff --git a/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-64.png b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-64.png new file mode 100644 index 0000000..49a0170 Binary files /dev/null and b/Space HUD/Assets.xcassets/AppIcon.appiconset/spacebar-64.png differ diff --git a/Space HUD/Assets.xcassets/Contents.json b/Space HUD/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Space HUD/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Space HUD/Assets.xcassets/space-logo.imageset/Contents.json b/Space HUD/Assets.xcassets/space-logo.imageset/Contents.json new file mode 100644 index 0000000..04e4c1b --- /dev/null +++ b/Space HUD/Assets.xcassets/space-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "space-logo.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Space HUD/Assets.xcassets/space-logo.imageset/space-logo.png b/Space HUD/Assets.xcassets/space-logo.imageset/space-logo.png new file mode 100644 index 0000000..b611866 Binary files /dev/null and b/Space HUD/Assets.xcassets/space-logo.imageset/space-logo.png differ diff --git a/Space HUD/Assets.xcassets/space-man.imageset/Contents.json b/Space HUD/Assets.xcassets/space-man.imageset/Contents.json new file mode 100644 index 0000000..43346be --- /dev/null +++ b/Space HUD/Assets.xcassets/space-man.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Untitled.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Space HUD/Assets.xcassets/space-man.imageset/Untitled.png b/Space HUD/Assets.xcassets/space-man.imageset/Untitled.png new file mode 100644 index 0000000..34ae904 Binary files /dev/null and b/Space HUD/Assets.xcassets/space-man.imageset/Untitled.png differ diff --git a/Space HUD/Base.lproj/Main.storyboard b/Space HUD/Base.lproj/Main.storyboard new file mode 100644 index 0000000..bd678c0 --- /dev/null +++ b/Space HUD/Base.lproj/Main.storyboard @@ -0,0 +1,717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Space HUD/Extensions/ColorExtensions.swift b/Space HUD/Extensions/ColorExtensions.swift new file mode 100644 index 0000000..64566f6 --- /dev/null +++ b/Space HUD/Extensions/ColorExtensions.swift @@ -0,0 +1,36 @@ +// +// ColorExtensions.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import SwiftUI + +extension Color { + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (1, 1, 1, 0) + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} diff --git a/Space HUD/Extensions/Constants.swift b/Space HUD/Extensions/Constants.swift new file mode 100644 index 0000000..3a8ad7e --- /dev/null +++ b/Space HUD/Extensions/Constants.swift @@ -0,0 +1,14 @@ +// +// Constants.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation + +struct Constants { + + static var issueStatuses = [IssueStatus]() + +} diff --git a/Space HUD/Extensions/DefaultsEtension.swift b/Space HUD/Extensions/DefaultsEtension.swift new file mode 100644 index 0000000..2b138ef --- /dev/null +++ b/Space HUD/Extensions/DefaultsEtension.swift @@ -0,0 +1,21 @@ +// +// DefaultsEtension.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import Defaults +import KeychainAccess + +extension Defaults.Keys { + + static let orgName = Key("orgName", default: "") + static let token = Key("token", default: "") + static let projectId = Key("projectId", default: "") +} + +extension KeychainKeys { + static let token: KeychainAccessKey = KeychainAccessKey(key: "token") +} diff --git a/Space HUD/Extensions/ViewExtensions.swift b/Space HUD/Extensions/ViewExtensions.swift new file mode 100644 index 0000000..7f0060a --- /dev/null +++ b/Space HUD/Extensions/ViewExtensions.swift @@ -0,0 +1,71 @@ +// +// ViewExtensions.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import SwiftUI + +extension View { + func roundedCorners(radius: CGFloat, corners: RectCorner) -> some View { + clipShape( RoundedCornersShape(radius: radius, corners: corners) ) + } +} + +struct RectCorner: OptionSet { + + let rawValue: Int + + static let topLeft = RectCorner(rawValue: 1 << 0) + static let topRight = RectCorner(rawValue: 1 << 1) + static let bottomRight = RectCorner(rawValue: 1 << 2) + static let bottomLeft = RectCorner(rawValue: 1 << 3) + + static let allCorners: RectCorner = [.topLeft, topRight, .bottomLeft, .bottomRight] +} + + +struct RoundedCornersShape: Shape { + + var radius: CGFloat = .zero + var corners: RectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + var path = Path() + + let p1 = CGPoint(x: rect.minX, y: corners.contains(.topLeft) ? rect.minY + radius : rect.minY ) + let p2 = CGPoint(x: corners.contains(.topLeft) ? rect.minX + radius : rect.minX, y: rect.minY ) + + let p3 = CGPoint(x: corners.contains(.topRight) ? rect.maxX - radius : rect.maxX, y: rect.minY ) + let p4 = CGPoint(x: rect.maxX, y: corners.contains(.topRight) ? rect.minY + radius : rect.minY ) + + let p5 = CGPoint(x: rect.maxX, y: corners.contains(.bottomRight) ? rect.maxY - radius : rect.maxY ) + let p6 = CGPoint(x: corners.contains(.bottomRight) ? rect.maxX - radius : rect.maxX, y: rect.maxY ) + + let p7 = CGPoint(x: corners.contains(.bottomLeft) ? rect.minX + radius : rect.minX, y: rect.maxY ) + let p8 = CGPoint(x: rect.minX, y: corners.contains(.bottomLeft) ? rect.maxY - radius : rect.maxY ) + + + path.move(to: p1) + path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), + tangent2End: p2, + radius: radius) + path.addLine(to: p3) + path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY), + tangent2End: p4, + radius: radius) + path.addLine(to: p5) + path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), + tangent2End: p6, + radius: radius) + path.addLine(to: p7) + path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.maxY), + tangent2End: p8, + radius: radius) + path.closeSubpath() + + return path + } +} diff --git a/Space HUD/KeyChain.swift b/Space HUD/KeyChain.swift new file mode 100644 index 0000000..64c9664 --- /dev/null +++ b/Space HUD/KeyChain.swift @@ -0,0 +1,93 @@ +// +// KeyChain.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +import KeychainAccess +import Foundation + +typealias FromKeychain = KeychainStorage +typealias KeychainKeys = KeychainAccessKey + +@propertyWrapper +struct KeychainStorage: DynamicProperty { + + private let key: KeychainAccessKey + @ObservedObject + private var observable: ObservableString + + init(wrappedValue: String = "", _ key: KeychainAccessKey) { + self.key = key + + let presentObservable: ObservableString? = ObservablesStore.store[key] + + if presentObservable != nil { + self.observable = presentObservable! + } else { + self.observable = ObservableString(key) + ObservablesStore.store[key] = self.observable + } + } + + var wrappedValue: String { + get { observable.value } + + nonmutating set { + observable.value = newValue + } + } + + var projectedValue: Binding { $observable.value } +} + +private class ObservableString: ObservableObject { + + let key: KeychainAccessKey + var currentValue: String? = nil + + init(_ key: KeychainAccessKey) { + self.key = key + } + + var value: String { + get { + if currentValue == nil { + currentValue = try? Keychain().get(key.keyName) ?? "" + } + + return currentValue! + } + + set { + objectWillChange.send() + + do { + currentValue = newValue + + if currentValue!.isEmpty { + // Keychain does not want to save empty value + try Keychain().remove(key.keyName) + } else { + try Keychain().set(currentValue!, key: key.keyName) + } + } catch let error { + fatalError("\(error)") + } + } + } +} + +struct KeychainAccessKey: Hashable { + let keyName: String + + init(key: String) { + self.keyName = key + } +} + +private struct ObservablesStore { + static var store: [KeychainAccessKey: ObservableString] = [:] +} diff --git a/Space HUD/Notifications.swift b/Space HUD/Notifications.swift new file mode 100644 index 0000000..b250304 --- /dev/null +++ b/Space HUD/Notifications.swift @@ -0,0 +1,32 @@ +// +// Notifications.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import UserNotifications + +func sendNotification(subtitle: String = "", body: String = "") { + let content = UNMutableNotificationContent() + content.title = "SpaceBar" + + if !subtitle.isEmpty { + content.subtitle = subtitle + } + + if body.count > 0 { + content.body = body + } + + let uuidString = UUID().uuidString + let request = UNNotificationRequest( + identifier: uuidString, + content: content, trigger: nil) + + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.requestAuthorization(options: [.alert, .sound]) { _, _ in } + notificationCenter.add(request) +} + diff --git a/Space HUD/Space/Client.swift b/Space HUD/Space/Client.swift new file mode 100644 index 0000000..ab4bd5e --- /dev/null +++ b/Space HUD/Space/Client.swift @@ -0,0 +1,306 @@ +// +// Client.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import Alamofire +import Defaults +import KeychainAccess + +public class SpaceClient { + @Default(.orgName) var orgName + @Default(.projectId) var projectId + @FromKeychain(.token) var token + + func getTodos(completion:@escaping (([TodoItemRecord]) -> Void)) -> Void { + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request("https://\(orgName).jetbrains.space/api/http/todo", method: .get, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: Resp.self) { response in + switch response.result { + case .success(let resp): + completion(resp.data) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get ToDos", + debubMessage: response.debugDescription) + completion([TodoItemRecord]()) + } + } + } + + func createTodo(text: String, completion:@escaping (() -> Void)) -> Void { + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + let parameters = [ + "text": text + ] as [String: Any] + AF.request("https://\(orgName).jetbrains.space/api/http/todo", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: TodoItemRecord.self) { response in + switch response.result { + case .success(_): + completion() + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Create ToDo", + debubMessage: response.debugDescription) + completion() + } + } + } + + func deleteTodo(id: String, completion:@escaping (() -> Void)) -> Void { + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request("https://\(orgName).jetbrains.space/api/http/todo/\(id)", method: .delete, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .response() { response in + switch response.result { + case .success(_): + completion() + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Delete ToDo item with id \(id)", + debubMessage: response.debugDescription) + completion() + } + } + } + + func toggleTodoStatus(todo: TodoItemRecord, completion:@escaping (() -> Void)) -> Void { + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + let parameters = [ + "open": "\(todo._status != "Open")", + ] as [String: Any] + + AF.request("https://\(orgName).jetbrains.space/api/http/todo/\(todo.id)", method: .patch, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .response() { response in + switch response.result { + case .success(_): + completion() + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Toggle status of ToDo item", + debubMessage: response.debugDescription) + completion() + + } + } + } + + + func getIssues(statusId: String = "", type: String = "", completion:@escaping (([Issue]) -> Void)) -> Void { + NSLog("Getting Issues") + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + var url = "https://\(orgName).jetbrains.space/api/http/projects/\(projectId)/planning/issues?sorting=UPDATED&descending=true" + + if !statusId.isEmpty { + url += "&statuses=\(statusId)" + } + + if type == "assigned" { + url += "&assigneeId=me" + } else if type == "created" { + url += "&createdByProfileId=me" + } + AF.request(url, + method: .get, + encoding: JSONEncoding.default, + headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: Resp.self) { response in + switch response.result { + case .success(let resp): + completion(resp.data) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get issues", + debubMessage: response.debugDescription) + completion([Issue]()) + } + } + } + + + func getIssueStatuses(completion:@escaping (([IssueStatus]) -> Void)) -> Void { + NSLog("Getting Issue Statuses") + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request("https://\(orgName).jetbrains.space/api/http/projects/\(projectId)/planning/issues/statuses", method: .get, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: [IssueStatus].self) { response in + switch response.result { + case .success(let resp): + completion(resp) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get issue statuses", + debubMessage: response.debugDescription) + completion([IssueStatus]()) + } + } + } + + func updateIssueStatus(issueId: String, newStatusId: String, completion:@escaping (() -> Void)) -> Void { + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + let parameters = [ + "status": newStatusId, + ] as [String: Any] + + AF.request("https://\(orgName).jetbrains.space/api/http/projects/\(projectId)/planning/issues/\(issueId)", method: .patch, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .response() { response in + switch response.result { + case .success(_): + completion() + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Update issue with id \(issueId) to status \(newStatusId)", + debubMessage: response.debugDescription) + completion() + } + } + } + + func getCodeReviews(type: String = "", completion:@escaping (([String]) -> Void)) -> Void { + NSLog("Getting Code Reviews") + + var url = "https://\(orgName).jetbrains.space/api/http/projects/\(projectId)/code-reviews?sorting=UPDATED&descending=true" + + if type == "reviewRequested" { + url += "&reviewer=me" + } else if type == "created" { + url += "&author=me" + } + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request(url, method: .get, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: Resp.self) { response in + switch response.result { + case .success(let resp): + completion(resp.data.map{ r in r.review.id}) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get Code Reviews", + debubMessage: response.debugDescription) + completion([String]()) + } + } + } + + func getCodeReview(id: String, completion:@escaping ((CodeReviewRecord?) -> Void)) -> Void { + NSLog("Getting Code Review by id \(id)") + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request("https://\(orgName).jetbrains.space/api/http/projects/\(projectId)/code-reviews/\(id)", method: .get, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: CodeReviewRecord.self) { response in + switch response.result { + case .success(let resp): + completion(resp) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get Code Review with id \(id)", + debubMessage: response.debugDescription) + + completion(nil) + } + } + } + + func getProjects(completion:@escaping (([Project]) -> Void)) -> Void { + NSLog("Getting Projects") + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request("https://\(orgName).jetbrains.space/api/http/projects", method: .get, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: Resp.self) { response in + switch response.result { + case .success(let resp): + completion(resp.data) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get projects", + debubMessage: response.debugDescription) + completion([Project]()) + } + } + } + + func getProjectById(id: String, completion:@escaping ((Project?) -> Void)) -> Void { + NSLog("Getting Project by id: \(id)") + + let headers: HTTPHeaders = [ + .authorization(bearerToken: token), + .accept("application/json") + ] + + AF.request("https://\(orgName).jetbrains.space/api/http/projects/\(id)", method: .get, encoding: JSONEncoding.default, headers: headers) + .validate(statusCode: 200..<300) + .responseDecodable(of: Project.self) { response in + switch response.result { + case .success(let resp): + completion(resp) + case .failure(let error): + self.handleError(error: error, + notificationSubTitle: "Get project by id: \(id)", + debubMessage: response.debugDescription) + completion(nil) + } + } + } + + private func handleError(error: AFError, notificationSubTitle: String, debubMessage: String) { + sendNotification(subtitle: notificationSubTitle, body: error.localizedDescription) + NSLog("\(notificationSubTitle): \(debubMessage)") + } +} diff --git a/Space HUD/Space/Dtos.swift b/Space HUD/Space/Dtos.swift new file mode 100644 index 0000000..7c713af --- /dev/null +++ b/Space HUD/Space/Dtos.swift @@ -0,0 +1,151 @@ +// +// Dtos.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import Defaults + +struct Resp: Codable { + var next: String + var totalCount: Int + var data: [T] + + enum CodingKeys: String, CodingKey { + case next + case totalCount + case data + } +} + +struct TodoItemRecord: Codable, Identifiable, Hashable { + var id: String + var archived: Bool + var content: TodoItemContent + var _status: String + + enum CodingKeys: String, CodingKey { + case id + case archived + case content + case _status + } +} + +struct TodoItemContent: Codable, Hashable { + var text: String + + enum CodingKeys: String, CodingKey { + case text + } +} + +struct Issue: Codable, Identifiable, Hashable { + var id: String + var title: String + var status: FieldWithId + var number: Int + var createdBy: User + + enum CodingKeys: String, CodingKey { + case id + case title + case number + case status + case createdBy + } +} + +struct FieldWithId: Codable, Hashable { + var id: String + + enum CodingKeys: String, CodingKey { + case id + } +} + +struct IssueStatus: Codable, Identifiable, Hashable { + var id: String + var archived: Bool + var name: String + var resolved: Bool + var color: String + + enum CodingKeys: String, CodingKey { + case id + case archived + case name + case resolved + case color + } +} + +struct CodeReviewRecord: Codable, Identifiable, Hashable { + var id: String + var project: ProjectKey + var projectId: String + var number: Int + var title: String + var state: String + var branchPairs: [MergeRequestBranchPair] + + enum CodingKeys: String, CodingKey { + case id + case project + case projectId + case number + case title + case state + case branchPairs + } +} + +struct ProjectKey: Codable, Hashable { + var key: String + + enum CodingKeys: String, CodingKey { + case key + } +} + +struct CodeReviewWithCount: Codable, Hashable { + var review: FieldWithId + + enum CodingKeys: String, CodingKey { + case review + } +} + +struct MergeRequestBranchPair: Codable, Hashable { + var repository: String + var sourceBranch: String + var targetBranch: String + + enum CodingKeys: String, CodingKey { + case repository + case sourceBranch + case targetBranch + } +} + +struct Project: Codable, Hashable, Identifiable, Defaults.Serializable { + var id: String + var name: String + var icon: String? + + enum CodingKeys: String, CodingKey { + case id + case name + case icon + } +} + +struct User: Codable, Hashable { + var name: String + + enum CodlingKeys: String, CodingKey { + case name + } +} diff --git a/Space HUD/Space/ViewModel.swift b/Space HUD/Space/ViewModel.swift new file mode 100644 index 0000000..3d98159 --- /dev/null +++ b/Space HUD/Space/ViewModel.swift @@ -0,0 +1,91 @@ +// +// ViewModel.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import Defaults + +class Model: ObservableObject{ + private var client = SpaceClient() + + @Default(.projectId) var projectId + + @Published var todos: [TodoItemRecord] = [] + @Published var issues: [Issue] = [] + @Published var codeReviews: [CodeReviewRecord] = [] + @Published var projects: [Project] = [] + @Published var projectName: String = "" + @Published var iconName: String? + @Published var selected: String? + + @Published var selectedStatusId: String = "" + @Published var selectedType: String = "" + + init() { + NSLog("Model initialized") + } + + func initialize() { + client.getIssueStatuses{ resp in + Constants.issueStatuses = resp + } + + client.getProjectById(id: projectId) { project in + if let pr = project { + self.projectName = pr.name + self.iconName = pr.icon + } + } + } + + func refresh(){ + getTodos() + getIssues(type: "assigned") + getCodeReviews() + client.getIssueStatuses{ resp in + Constants.issueStatuses = resp + } + } + + func getTodos() { + client.getTodos() { t in + self.todos = t.sorted { + $0._status > $1._status + } + } + } + + func getProjects() { + client.getProjects() { ps in + self.projects = ps + } + } + + func getIssues(statusId: String = "", type: String = "") { + client.getIssues(statusId: selectedStatusId, type: selectedType) { ps in + self.issues = ps + } + } + + func getCodeReviews(type: String = "") { + client.getCodeReviews(type: type) { crIds in + self.codeReviews.removeAll() + crIds.forEach { id in + self.client.getCodeReview(id: id) { cr in + if let codeReview = cr { + self.codeReviews.append(codeReview) + } + } + } + } + } + + func setIssues(issues: [Issue]) { + self.issues = issues + } + + +} diff --git a/Space HUD/Space_HUD.entitlements b/Space HUD/Space_HUD.entitlements new file mode 100644 index 0000000..625af03 --- /dev/null +++ b/Space HUD/Space_HUD.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + + diff --git a/Space HUD/Views/AboutView.swift b/Space HUD/Views/AboutView.swift new file mode 100644 index 0000000..bd8d935 --- /dev/null +++ b/Space HUD/Views/AboutView.swift @@ -0,0 +1,25 @@ +// +// AboutView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI + +struct AboutView: View { + + let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String + + var body: some View { + Text("Space HUD - macOS application which simiplifies interaction with JetBrains Space") + Text("version: \(currentVersion)") + Text("Made by [Pavel Makhov](https://github.com/streetturtle)") + } +} + +struct AboutView_Previews: PreviewProvider { + static var previews: some View { + AboutView() + } +} diff --git a/Space HUD/Views/CodeReviewView.swift b/Space HUD/Views/CodeReviewView.swift new file mode 100644 index 0000000..9933ef8 --- /dev/null +++ b/Space HUD/Views/CodeReviewView.swift @@ -0,0 +1,75 @@ +// +// CodeReviewView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +import Defaults + +struct CodeReviewView: View { + @Default(.orgName) var orgName + @State private var isHovering = false + + var codeReview: CodeReviewRecord? + + var body: some View { + Link(destination: URL(string: "https://\(orgName).jetbrains.space/p/spcbr/reviews/\(String(codeReview?.number ?? 1))")!) { + + VStack(alignment: .leading, spacing: 3) { + HStack { + Text(codeReview?.title) + .foregroundColor(.primary) + .multilineTextAlignment(.leading) + .font(.headline) + Text("#" + String(codeReview?.number ?? 1)) + .foregroundColor(.secondary) + } + + HStack(spacing: 20) { + Text(codeReview?.state) + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(isHovering ? Color.black : Color.blue) + .overlay(RoundedRectangle(cornerRadius: 50).stroke(isHovering ? Color.black : Color.blue)) + .foregroundColor(.secondary) + .font(.subheadline) + + HStack(spacing: 2) { + Text(codeReview?.branchPairs.first?.targetBranch) + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(isHovering ? Color.black : Color.blue) + .background(RoundedRectangle(cornerRadius: 50, style: .continuous).fill(Color.secondary.opacity(0.3))) + .font(.subheadline) + Image(systemName: "arrow.left") + Text(codeReview?.branchPairs.first?.sourceBranch) + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(isHovering ? Color.black : Color.blue) + .background(RoundedRectangle(cornerRadius: 50, style: .continuous).fill(Color.secondary.opacity(0.3))) + .font(.subheadline) + } + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .onHover { over in + isHovering = over + } + .padding(4) + .background( + isHovering + ? RoundedRectangle(cornerRadius: 4, style: .continuous).fill(Color.accentColor) + : RoundedRectangle(cornerRadius: 4, style: .continuous).fill(Color.accentColor.opacity(0)) + ) + + } +} + +struct CodeReviewView_Previews: PreviewProvider { + static var previews: some View { + CodeReviewView() + } +} diff --git a/Space HUD/Views/CodeReviewsView.swift b/Space HUD/Views/CodeReviewsView.swift new file mode 100644 index 0000000..9260c5d --- /dev/null +++ b/Space HUD/Views/CodeReviewsView.swift @@ -0,0 +1,74 @@ +// +// CodeReviewsView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +//import SkeletonUI + +struct CodeReviewsView: View { + + @ObservedObject var model: Model + + @Binding var selectedCrType: String + + + var body: some View { + VStack { + List(model.codeReviews) { codeReview in + CodeReviewView(codeReview: codeReview) + } + + HStack(alignment: .bottom) { + HStack(spacing: 0) { + Button(action: { + if selectedCrType == "reviewRequested" + { + model.getCodeReviews() + selectedCrType = "" + } else { + model.getCodeReviews(type: "reviewRequested") + selectedCrType = "reviewRequested" + } + }) + { + Text("Review Requested") + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(.secondary) + .background(selectedCrType == "reviewRequested" ? Color.secondary.opacity(0.5) : Color.red.opacity(0)) + .roundedCorners(radius: 10, corners: [.topLeft, .bottomLeft]) + + .font(.subheadline) + .padding([.top, .bottom], 8) + } + .buttonStyle(PlainButtonStyle()) + + Button(action: { + if selectedCrType == "created" + { + model.getCodeReviews() + selectedCrType = "" + } else { + model.getCodeReviews(type: "created") + selectedCrType = "created" + } + }) + { + Text("Created") + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(.secondary) + .background(selectedCrType == "created" ? Color.secondary.opacity(0.5) : Color.red.opacity(0)) + .roundedCorners(radius: 10, corners: [.topRight, .bottomRight]) + .font(.subheadline) + .padding([.top, .bottom], 8) + } + .buttonStyle(PlainButtonStyle()) + } + } + } + } +} diff --git a/Space HUD/Views/ContentView.swift b/Space HUD/Views/ContentView.swift new file mode 100644 index 0000000..52b0615 --- /dev/null +++ b/Space HUD/Views/ContentView.swift @@ -0,0 +1,115 @@ +// +// ContentView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +import Defaults + +struct ContentView: View { + + @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @State var selectedStatusId = "" + @State var type = "assigned" + @State var cRType = "reviewRequested" + @ObservedObject var model: Model + + @Default(.orgName) var orgName + @Default(.projectId) var projectId + var body: some View { + + NavigationView { + List { + VStack(alignment: .center) { + Image("space-logo") + .resizable() + .frame(width: 40, height: 40, alignment: .center) + + Text(orgName) + .fontWeight(.bold) + .font(.system(size: 20)) + + Picker("", selection: $projectId, content: { + ForEach(model.projects, id: \.id) { i in + Text("\(i.name)").tag(i) + } + }).labelsHidden() + .pickerStyle(MenuPickerStyle()) + .frame(width: 140) + .onAppear{ + self.model.getProjects() + } + .onChange(of: projectId) { id in + model.selected = "" + model.selectedType = "" + model.selectedStatusId = "" + model.refresh() + } + + } + Section ("Features") { + NavigationLink(destination: IssuesView(model: model), + tag: "1", + selection: $model.selected) { + NavLink(text: "Issues", count: "\(model.issues.count)", systemName: "staroflife.fill") + } + + NavigationLink(destination: CodeReviewsView(model: model, selectedCrType: $cRType), + tag: "2", + selection: $model.selected) { + NavLink(text: "Code Reviews", count: "\(model.codeReviews.count)", systemName: "highlighter") + } + + NavigationLink(destination: ToDosView(model: model), + tag: "3", + selection: $model.selected) { + NavLink(text: "ToDos", count: "\(model.todos.count)", systemName: "bolt.fill") } + } + + Section("App"){ + NavigationLink{ SettingsView()} label: { Label("Settings", systemImage: "gearshape") } + NavigationLink{ AboutView()} label: { Label("About", systemImage: "info") } + Label("Quit", systemImage: "power") + .onTapGesture { + appDelegate.quit() + } + } + } + .listStyle(.sidebar) + .frame(width: 180) + + ZStack { + Text("No selection") + .foregroundColor(.secondary) + .font(.headline) + VStack { + Spacer() + HStack { + Spacer() + Image("space-man") + .resizable() + .frame(width: 150, height: 150) + .opacity(0.5) + } + } + } + + }.onAppear { + NSLog("Content view appeared") + } + } +} + +struct CustomText: View { + let text: String + + var body: some View { + Text(text) + } + + init(_ text: String) { + self.text = text + } +} diff --git a/Space HUD/Views/IssueStatusView.swift b/Space HUD/Views/IssueStatusView.swift new file mode 100644 index 0000000..8d38fa7 --- /dev/null +++ b/Space HUD/Views/IssueStatusView.swift @@ -0,0 +1,48 @@ +// +// IssueStatusView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI + +struct IssueStatusView: View { + + @State private var isHovering = false + + @State var issue: Issue + @ObservedObject var model: Model + var body: some View { + Menu { + ForEach(Constants.issueStatuses) {status in + Button(status.name, action:{ + SpaceClient().updateIssueStatus(issueId: issue.id, newStatusId: status.id) { + model.getIssues() + issue.status.id = status.id + + } + }) + } + } + label: { + Text(Constants.issueStatuses.filter{i in i.id == issue.status.id}.first?.name ?? "") + .font(.subheadline) + } + .scaledToFit() + .menuStyle(BorderlessButtonMenuStyle()) + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .background( + RoundedRectangle(cornerRadius: 50, style: .continuous).fill(Color(hex: (Constants.issueStatuses.filter{i in i.id == issue.status.id}.first?.color ?? "333333"))) + ) + .foregroundColor(.secondary) + + } +} + +//struct IssueStatusView_Previews: PreviewProvider { +// static var previews: some View { +// IssueStatusView(issue: Issue(id: "123", title: "Issue Title", status: <#T##FieldWithId#>, number: <#T##Int#>)) +// } +//} diff --git a/Space HUD/Views/IssueView.swift b/Space HUD/Views/IssueView.swift new file mode 100644 index 0000000..7e8069f --- /dev/null +++ b/Space HUD/Views/IssueView.swift @@ -0,0 +1,54 @@ +// +// IssueView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import Foundation +import SwiftUI +import Defaults + +struct IssueView: View { + + @Default(.orgName) var orgName + @State private var isHovering = false + + @State var issue: Issue? + @ObservedObject var model: Model + + var body: some View { + + VStack(alignment: .leading, spacing: 3) { + HStack { + Link(destination: URL(string: "https://\(orgName).jetbrains.space/p/spcbr/issues/\(String(issue?.number ?? 1))")!) { + Text(issue?.title) + .foregroundColor(.primary) + .multilineTextAlignment(.leading) + .font(.headline) + Text("#" + String(issue?.number ?? 1)) + .foregroundColor(.secondary) + .font(.subheadline) + } + } + HStack { + IssueStatusView(issue: issue!, model: model) + Text("created by \(issue?.createdBy.name ?? "")") + .foregroundColor(.secondary) + .font(.subheadline) + } + + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .onHover { over in + isHovering = over + } + .padding(8) + .background( + isHovering + ? RoundedRectangle(cornerRadius: 4, style: .continuous).fill(Color.secondary.opacity(0.1)) + : RoundedRectangle(cornerRadius: 4, style: .continuous).fill(Color.accentColor.opacity(0)) + ) + + } +} diff --git a/Space HUD/Views/IssuesView.swift b/Space HUD/Views/IssuesView.swift new file mode 100644 index 0000000..76e82c7 --- /dev/null +++ b/Space HUD/Views/IssuesView.swift @@ -0,0 +1,130 @@ +// +// IssuesView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +//import SkeletonUI + +struct IssuesView: View { + @ObservedObject var model: Model + @State private var hoveringOver = false + + var body: some View { + VStack(spacing: 0) { + + List(model.issues) { issue in + IssueView(issue: issue, model: model) + } + + HStack(alignment: .bottom) { + HStack(spacing: 0) { + Button(action: { + if model.selectedType == "assigned" + { + model.selectedType = "" + model.getIssues() + } else { + model.selectedType = "assigned" + model.getIssues() + } + + }) + { + Text("Assigned") + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(.secondary) + .background(model.selectedType == "assigned" ? Color.secondary.opacity(0.5) : Color.red.opacity(0)) + .roundedCorners(radius: 10, corners: [.topLeft, .bottomLeft]) + .font(.subheadline) + } + .buttonStyle(PlainButtonStyle()) + + Button(action: { + if model.selectedType == "created" + { + model.selectedType = "" + model.getIssues() + } else { + model.selectedType = "created" + model.getIssues() + } + + }) + { + Text("Created") + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .foregroundColor(.secondary) + .background(model.selectedType == "created" ? Color.secondary.opacity(0.5) : Color.red.opacity(0)) + .roundedCorners(radius: 10, corners: [.topRight, .bottomRight]) + .font(.subheadline) + } + .buttonStyle(PlainButtonStyle()) + } + .overlay(RoundedRectangle(cornerRadius: 50).stroke(Color.secondary)) + .padding([.top, .bottom], 8) + + + Spacer() + + HStack(spacing: 0) { + ForEach(Constants.issueStatuses, id: \.id) { status in + + Button(action: { + if model.selectedStatusId == status.id { + model.selectedStatusId = "" + model.getIssues() + } else { + model.selectedStatusId = status.id + model.getIssues() + } + }) + { + Text(status.name) + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .background(model.selectedStatusId == status.id ? Color(hex: status.color) : Color.red.opacity(0)) + .roundedCorners(radius: 10, corners: getCorners(status: status) + ) + .foregroundColor(.secondary) + .font(.subheadline) + + } + .buttonStyle(PlainButtonStyle()) + } + } + .overlay(RoundedRectangle(cornerRadius: 50).stroke(Color.secondary)) + .padding([.top, .bottom], 8) + + } + .padding([.leading, .trailing], 16) + + } + } + + func getCorners(status: IssueStatus) -> RectCorner { + if (Constants.issueStatuses.first?.id == status.id) { + return [.topLeft, .bottomLeft] + } else if (Constants.issueStatuses.last?.id == status.id) { + return [.topRight, .bottomRight] + } else { + return [] + } + } +} + +//struct IssuesView_Previews: PreviewProvider { +// static var model = Model() +// static let issues = [ +// Issue(id: "1a2b", title: "Title", status: FieldWithId(id: "123"), number: 1) +// ] +// +// static var previews: some View { +// IssuesView(model: model, selectedStatus: .constant("asd")) +// } +//} + diff --git a/Space HUD/Views/NavLink.swift b/Space HUD/Views/NavLink.swift new file mode 100644 index 0000000..94dbbf0 --- /dev/null +++ b/Space HUD/Views/NavLink.swift @@ -0,0 +1,23 @@ +// +// NavLink.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI + +struct NavLink: View { + var text: String + var count: String + var systemName: String + + var body: some View { + Label { + Text(text) + .badge(count) + } icon: { + Image(systemName: systemName) + } + } +} diff --git a/Space HUD/Views/SettingsView.swift b/Space HUD/Views/SettingsView.swift new file mode 100644 index 0000000..2e75a94 --- /dev/null +++ b/Space HUD/Views/SettingsView.swift @@ -0,0 +1,80 @@ +// +// SettingsView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +import Defaults +import KeychainAccess + +struct SettingsView: View { + + @Default(.orgName) var orgName + @FromKeychain(.token) var token + + @Default(.projectId) var projectId + + @State private var selection = 1 + @ObservedObject var model = Model() + + @State var orgNameFieldValue = "" + + var body: some View { + Form { + Section { + VStack(alignment: .leading) { + HStack(alignment: .center) { + Text("Organization:").frame(width: 90, alignment: .trailing) + TextField("", text: $orgNameFieldValue) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .disableAutocorrection(true) + .textContentType(.password) + .multilineTextAlignment(.trailing) + .frame(width: 140) + .onAppear { + orgNameFieldValue = orgName + } + .onChange(of: orgNameFieldValue, perform: { newVal in + orgName = orgNameFieldValue + self.model.projects.removeAll() + self.model.getProjects() + }) + Text(".jetbrains.space") + } + + HStack(alignment: .center) { + Text("Required\nPermissions:") + .frame(width: 90, alignment: .trailing) + Text("Manage issue settings, Read Git repositories, Update issues, View code reviews, View issues, View project details") + .padding() + } + + HStack(alignment: .center) { + Text("Token:") + .frame(width: 90, alignment: .trailing) + VStack { + TextEditor(text: $token) + .frame(height: 150) + .padding(8) + .onChange(of: token) { _ in + model.getProjects() + } + } + .cornerRadius(10) + .padding(8) + .shadow(radius: 5) + } + } + } + } + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView() + } +} + diff --git a/Space HUD/Views/TodoView.swift b/Space HUD/Views/TodoView.swift new file mode 100644 index 0000000..8e83034 --- /dev/null +++ b/Space HUD/Views/TodoView.swift @@ -0,0 +1,77 @@ +// +// TodoView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI + +struct TodoView: View { + + var todo: TodoItemRecord? + var loading: Bool + var model: Model? + @State private var isHovering = false + + var body: some View { + HStack { + Button(action: { + if let t = todo { + SpaceClient().toggleTodoStatus(todo: t) { + model?.getTodos() + } + } + }) { + Image(systemName: (todo?._status == "Open") ? "square" : "checkmark.square") + .resizable() + .frame(width: 16, height: 16) + .padding(.top, 1) + }.buttonStyle(PlainButtonStyle()) + .skeleton(with: loading) + + + todo?._status == "Open" + ? Text(try! AttributedString(markdown: todo?.content.text.replacingOccurrences(of: "](/", with: "](https://streetturtle.jetbrains.space//") ?? "")) + .skeleton(with: loading) + : Text(try! AttributedString(markdown: todo?.content.text.replacingOccurrences(of: "](/", with: "](https://streetturtle.jetbrains.space//") ?? "")) + .foregroundColor(.secondary).strikethrough() + .skeleton(with: loading) + + + Spacer() + Button(action: { + if let id = todo?.id { + SpaceClient().deleteTodo(id: id) { + model?.getTodos() + } + } + }) { + + if isHovering { + Image(systemName: "trash") + .resizable() + .frame(width: 16, height: 16) + .padding(.top, 1) + .foregroundColor(.red) + } + } + .buttonStyle(PlainButtonStyle()) + } + .padding(4) + .onHover { over in + isHovering = over + } + .background( + isHovering + ? RoundedRectangle(cornerRadius: 4, style: .continuous).fill(Color.secondary.opacity(0.5)) + : RoundedRectangle(cornerRadius: 4, style: .continuous).fill(Color.accentColor.opacity(0)) + ) + } +} + +struct TodoView_Previews: PreviewProvider { + static var previews: some View { + TodoView(loading: false) + } +} diff --git a/Space HUD/Views/TodosView.swift b/Space HUD/Views/TodosView.swift new file mode 100644 index 0000000..81f36b0 --- /dev/null +++ b/Space HUD/Views/TodosView.swift @@ -0,0 +1,50 @@ +// +// TodosView.swift +// Space HUD +// +// Created by Pavel Makhov on 2022-11-18. +// + +import SwiftUI +import SkeletonUI + +struct ToDosView: View { + private let spaceClient = SpaceClient() + var model: Model + @State private var newTodo: String = "" + + private let text = "Add a task (**bold**, _italic_, [link text](link))" + + var body: some View { + VStack { + SkeletonList(with: model.todos, quantity: 3) { loading, todo in + TodoView(todo: todo, loading: loading, model: model) + } + + TextField(text, text: $newTodo) + .padding(.vertical, 8) + .padding(.horizontal, 8) + .padding(.leading, 22) + .cornerRadius(8) + .textFieldStyle(PlainTextFieldStyle()) + .overlay( + Image(systemName: "plus.circle.fill") + .frame(maxWidth: .infinity, alignment: .leading) + .foregroundColor(.gray) + .padding(.leading, 8) + ).onSubmit { + spaceClient.createTodo(text: newTodo) { + model.getTodos() + } + newTodo = "" + }.padding(.bottom, 8) + + } + } +} + +struct ToDosView_Previews: PreviewProvider { + static var previews: some View { + ToDosView(model: Model()) + } +}