Compare commits
7 Commits
9b99d9c1ff
...
4d67d8bffd
| Author | SHA1 | Date |
|---|---|---|
|
|
4d67d8bffd | |
|
|
5e4587909d | |
|
|
244da37318 | |
|
|
71e56f8d2b | |
|
|
239403e75c | |
|
|
55d8b50f1d | |
|
|
7047c51a07 |
|
|
@ -7,68 +7,68 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE; };
|
15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */; };
|
||||||
15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6; };
|
1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38 /* BugListPopover.swift */; };
|
||||||
58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581; };
|
1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2 /* URL+Feishu.swift */; };
|
||||||
4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A; };
|
29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */; };
|
||||||
DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85; };
|
29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6 /* FeishuService.swift */; };
|
||||||
A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718; };
|
3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4 /* BugRow.swift */; };
|
||||||
E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5; };
|
4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */; };
|
||||||
D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4; };
|
47ED652FA0AC818086F85DF3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED0A90DFCB8D4968E4E3CA1 /* Logger.swift */; };
|
||||||
4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C; };
|
485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */; };
|
||||||
CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3; };
|
4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A /* Bug.swift */; };
|
||||||
CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C; };
|
56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9 /* PollerService.swift */; };
|
||||||
29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6; };
|
58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */; };
|
||||||
6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33; };
|
6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33 /* NotificationService.swift */; };
|
||||||
56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9; };
|
64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */; };
|
||||||
B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C; };
|
7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D /* SettingsView.swift */; };
|
||||||
C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A; };
|
A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718 /* BugPriority.swift */; };
|
||||||
29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0; };
|
AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670 /* BugStore.swift */; };
|
||||||
64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8; };
|
B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */; };
|
||||||
EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D11523ACB81DBB9D412995; };
|
B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C /* TokenManager.swift */; };
|
||||||
47ED652FA0AC818086F85DF3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED0A90DFCB8D4968E4E3CA1; };
|
C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89 /* Assets.xcassets */; };
|
||||||
1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2; };
|
C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */; };
|
||||||
AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670; };
|
CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */; };
|
||||||
485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991; };
|
CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3 /* FeishuError.swift */; };
|
||||||
1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38; };
|
D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */; };
|
||||||
3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4; };
|
DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */; };
|
||||||
B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B; };
|
E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5 /* BugStatus.swift */; };
|
||||||
7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D; };
|
EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */; };
|
||||||
C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89; };
|
F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
EF0182DF85A7CD0319333F24 /* Bugger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bugger.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
207CA4381D0CFEC95263A2DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
|
||||||
DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = "<group>"; };
|
|
||||||
9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; };
|
|
||||||
846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = "<group>"; };
|
|
||||||
0B24E1E1D32F7B774D12DD85 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = "<group>"; };
|
|
||||||
5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = "<group>"; };
|
|
||||||
646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = "<group>"; };
|
|
||||||
F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = "<group>"; };
|
|
||||||
6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = "<group>"; };
|
|
||||||
2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = "<group>"; };
|
|
||||||
FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = "<group>"; };
|
|
||||||
21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = "<group>"; };
|
|
||||||
59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
|
||||||
4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = "<group>"; };
|
|
||||||
0E7E3775F77DD36C27160A8C /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; };
|
|
||||||
0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
|
|
||||||
87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = "<group>"; };
|
|
||||||
EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = "<group>"; };
|
|
||||||
34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOAuthServer.swift; sourceTree = "<group>"; };
|
|
||||||
0ED0A90DFCB8D4968E4E3CA1 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
|
||||||
F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = "<group>"; };
|
|
||||||
344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = "<group>"; };
|
|
||||||
6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = "<group>"; };
|
|
||||||
CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = "<group>"; };
|
|
||||||
1129D6F630904F03D06B5DC4 /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = "<group>"; };
|
|
||||||
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = "<group>"; };
|
|
||||||
2D3693AA93F823859D15256D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
0B24E1E1D32F7B774D12DD85 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = "<group>"; };
|
||||||
|
0E7E3775F77DD36C27160A8C /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; };
|
||||||
|
0ED0A90DFCB8D4968E4E3CA1 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||||
|
0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
1129D6F630904F03D06B5DC4 /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = "<group>"; };
|
||||||
|
207CA4381D0CFEC95263A2DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = "<group>"; };
|
||||||
|
2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = "<group>"; };
|
||||||
|
2D3693AA93F823859D15256D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = "<group>"; };
|
||||||
|
34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOAuthServer.swift; sourceTree = "<group>"; };
|
||||||
|
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = "<group>"; };
|
||||||
|
4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = "<group>"; };
|
||||||
|
5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = "<group>"; };
|
||||||
|
59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||||
|
646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = "<group>"; };
|
||||||
|
6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = "<group>"; };
|
||||||
|
6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = "<group>"; };
|
||||||
73AB234B54673C838E33AC7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
73AB234B54673C838E33AC7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = "<group>"; };
|
||||||
|
87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; };
|
||||||
|
CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = "<group>"; };
|
||||||
|
DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = "<group>"; };
|
||||||
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; };
|
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; };
|
||||||
|
EF0182DF85A7CD0319333F24 /* Bugger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bugger.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = "<group>"; };
|
||||||
|
F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = "<group>"; };
|
||||||
|
F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = "<group>"; };
|
||||||
|
FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
14ED1DECD6D906F498B9ECB5 /* Root */ = {
|
14ED1DECD6D906F498B9ECB5 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
287BE755981C092238309A9A /* Sources */,
|
287BE755981C092238309A9A /* Sources */,
|
||||||
|
|
@ -105,6 +105,17 @@
|
||||||
path = Sources;
|
path = Sources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
31DF436612EC1C3CD1194AA1 /* Views */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E7E4AD241EEE594C1EEB9724 /* FloatingWidget */,
|
||||||
|
EA9ED591BB8A18FEBD1B58A3 /* MenuBar */,
|
||||||
|
74AD95EF49A7D01C83865FAC /* OAuth */,
|
||||||
|
B70D2E54AD6B19DD455A8F82 /* Settings */,
|
||||||
|
);
|
||||||
|
path = Views;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
65997CD9BD6FFEF8851BF59E /* Models */ = {
|
65997CD9BD6FFEF8851BF59E /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -117,16 +128,30 @@
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
A8C46168E4DFCE29B056A4E2 /* Services */ = {
|
6AB9730AA11690EFC8F21473 /* ViewModels */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */,
|
344F52FB34B285D91A3E4670 /* BugStore.swift */,
|
||||||
59CC175DE8E9FE3250757D33 /* NotificationService.swift */,
|
|
||||||
4F4628E7B599B69B499E09E9 /* PollerService.swift */,
|
|
||||||
0E7E3775F77DD36C27160A8C /* TokenManager.swift */,
|
|
||||||
E0E23D3899D89889546FB428 /* Feishu */,
|
|
||||||
);
|
);
|
||||||
path = Services;
|
path = ViewModels;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
74AD95EF49A7D01C83865FAC /* OAuth */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */,
|
||||||
|
);
|
||||||
|
path = OAuth;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
8AD6A16612A2DB7B2F899787 /* Resources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
09B9A4D84F3F604C49171A89 /* Assets.xcassets */,
|
||||||
|
73AB234B54673C838E33AC7F /* Info.plist */,
|
||||||
|
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */,
|
||||||
|
);
|
||||||
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
A6646A2065F53ED50AD2CCA7 /* Utils */ = {
|
A6646A2065F53ED50AD2CCA7 /* Utils */ = {
|
||||||
|
|
@ -142,23 +167,32 @@
|
||||||
path = Utils;
|
path = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
6AB9730AA11690EFC8F21473 /* ViewModels */ = {
|
A8C46168E4DFCE29B056A4E2 /* Services */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
344F52FB34B285D91A3E4670 /* BugStore.swift */,
|
F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */,
|
||||||
|
59CC175DE8E9FE3250757D33 /* NotificationService.swift */,
|
||||||
|
4F4628E7B599B69B499E09E9 /* PollerService.swift */,
|
||||||
|
0E7E3775F77DD36C27160A8C /* TokenManager.swift */,
|
||||||
|
E0E23D3899D89889546FB428 /* Feishu */,
|
||||||
);
|
);
|
||||||
path = ViewModels;
|
path = Services;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
31DF436612EC1C3CD1194AA1 /* Views */ = {
|
B23D89B2D2DF3745A003AEB1 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E7E4AD241EEE594C1EEB9724 /* FloatingWidget */,
|
EF0182DF85A7CD0319333F24 /* Bugger.app */,
|
||||||
EA9ED591BB8A18FEBD1B58A3 /* MenuBar */,
|
|
||||||
74AD95EF49A7D01C83865FAC /* OAuth */,
|
|
||||||
B70D2E54AD6B19DD455A8F82 /* Settings */,
|
|
||||||
);
|
);
|
||||||
path = Views;
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
B70D2E54AD6B19DD455A8F82 /* Settings */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
2D3693AA93F823859D15256D /* SettingsView.swift */,
|
||||||
|
);
|
||||||
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E0E23D3899D89889546FB428 /* Feishu */ = {
|
E0E23D3899D89889546FB428 /* Feishu */ = {
|
||||||
|
|
@ -189,40 +223,6 @@
|
||||||
path = MenuBar;
|
path = MenuBar;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
74AD95EF49A7D01C83865FAC /* OAuth */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */,
|
|
||||||
);
|
|
||||||
path = OAuth;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B70D2E54AD6B19DD455A8F82 /* Settings */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
2D3693AA93F823859D15256D /* SettingsView.swift */,
|
|
||||||
);
|
|
||||||
path = Settings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
8AD6A16612A2DB7B2F899787 /* Resources */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
09B9A4D84F3F604C49171A89 /* Assets.xcassets */,
|
|
||||||
73AB234B54673C838E33AC7F /* Info.plist */,
|
|
||||||
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */,
|
|
||||||
);
|
|
||||||
path = Resources;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B23D89B2D2DF3745A003AEB1 /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
EF0182DF85A7CD0319333F24 /* Bugger.app */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
|
@ -353,15 +353,18 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
FEISHU_APP_ID = "$(FEISHU_APP_ID)";
|
FEISHU_APP_ID = cli_aacb82a1c0b8dcc0;
|
||||||
FEISHU_APP_SECRET = "$(FEISHU_APP_SECRET)";
|
FEISHU_APP_SECRET = jUkU9ygRuQ0KaZFV2BSPfe33iXCahzNM;
|
||||||
FEISHU_BASE_DOMAIN = "xorbitlab.feishu.cn";
|
FEISHU_BASE_DOMAIN = xmi5p6bgsnf8.feishu.cn;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = Resources/Info.plist;
|
INFOPLIST_FILE = Resources/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.1;
|
MARKETING_VERSION = 1.2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger;
|
PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|
@ -374,15 +377,18 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
FEISHU_APP_ID = "$(FEISHU_APP_ID)";
|
FEISHU_APP_ID = cli_aacb82a1c0b8dcc0;
|
||||||
FEISHU_APP_SECRET = "$(FEISHU_APP_SECRET)";
|
FEISHU_APP_SECRET = jUkU9ygRuQ0KaZFV2BSPfe33iXCahzNM;
|
||||||
FEISHU_BASE_DOMAIN = "xorbitlab.feishu.cn";
|
FEISHU_BASE_DOMAIN = xmi5p6bgsnf8.feishu.cn;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = Resources/Info.plist;
|
INFOPLIST_FILE = Resources/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.1;
|
MARKETING_VERSION = 1.2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger;
|
PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|
@ -412,7 +418,6 @@
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
};
|
};
|
||||||
rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */;
|
rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,8 @@
|
||||||
FEISHU_APP_ID = cli_your_app_id
|
FEISHU_APP_ID = cli_your_app_id
|
||||||
FEISHU_APP_SECRET = your_app_secret
|
FEISHU_APP_SECRET = your_app_secret
|
||||||
FEISHU_BASE_DOMAIN = xorbitlab.feishu.cn
|
FEISHU_BASE_DOMAIN = xorbitlab.feishu.cn
|
||||||
|
|
||||||
|
// Bitable app token & table ID (from the Bitable URL). Baked into the app at
|
||||||
|
// build time as defaults so internal users don't have to enter them in Settings.
|
||||||
|
FEISHU_APP_TOKEN = your_bitable_app_token
|
||||||
|
FEISHU_TABLE_ID = your_bitable_table_id
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.1</string>
|
<string>1.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>2</string>
|
<string>3</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>14.0</string>
|
<string>14.0</string>
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
|
|
@ -28,5 +28,9 @@
|
||||||
<string>$(FEISHU_APP_SECRET)</string>
|
<string>$(FEISHU_APP_SECRET)</string>
|
||||||
<key>FEISHU_BASE_DOMAIN</key>
|
<key>FEISHU_BASE_DOMAIN</key>
|
||||||
<string>$(FEISHU_BASE_DOMAIN)</string>
|
<string>$(FEISHU_BASE_DOMAIN)</string>
|
||||||
|
<key>FEISHU_APP_TOKEN</key>
|
||||||
|
<string>$(FEISHU_APP_TOKEN)</string>
|
||||||
|
<key>FEISHU_TABLE_ID</key>
|
||||||
|
<string>$(FEISHU_TABLE_ID)</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct AppConfig: Codable, Equatable {
|
struct AppConfig: Codable, Equatable {
|
||||||
var appToken: String = ""
|
var appToken: String = BundledDefault.appToken
|
||||||
var tableId: String = ""
|
var tableId: String = BundledDefault.tableId
|
||||||
var assigneeName: String = ""
|
var assigneeName: String = ""
|
||||||
var fieldMappings: FieldMappings = FieldMappings()
|
var fieldMappings: FieldMappings = FieldMappings()
|
||||||
var pollIntervalSeconds: Int = 300
|
var pollIntervalSeconds: Int = 300
|
||||||
|
|
@ -11,7 +11,11 @@ struct AppConfig: Codable, Equatable {
|
||||||
var dailyRefreshTimes: [String] = []
|
var dailyRefreshTimes: [String] = []
|
||||||
var showFloatingWidget: Bool = false
|
var showFloatingWidget: Bool = false
|
||||||
var launchAtLogin: Bool = true
|
var launchAtLogin: Bool = true
|
||||||
var feishuBaseDomain: String = "xorbitlab.feishu.cn"
|
var feishuBaseDomain: String = BundledDefault.feishuBaseDomain
|
||||||
|
/// When false (the default), Bugger waits for the next scheduled poll
|
||||||
|
/// instead of fetching immediately on launch. Avoids a crash-on-start
|
||||||
|
/// loop if the table fetch is failing.
|
||||||
|
var refreshOnStart: Bool = false
|
||||||
|
|
||||||
struct FieldMappings: Codable, Equatable {
|
struct FieldMappings: Codable, Equatable {
|
||||||
var titleField: String = "Title"
|
var titleField: String = "Title"
|
||||||
|
|
@ -49,4 +53,44 @@ struct AppConfig: Codable, Equatable {
|
||||||
var isConfigured: Bool {
|
var isConfigured: Bool {
|
||||||
!appToken.isEmpty && !tableId.isEmpty
|
!appToken.isEmpty && !tableId.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicit keys + tolerant decoding so a config persisted before a new
|
||||||
|
// field was added (missing key) doesn't fail to decode and wipe settings.
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case appToken, tableId, assigneeName, fieldMappings
|
||||||
|
case pollIntervalSeconds, dailyRefreshTimes
|
||||||
|
case showFloatingWidget, launchAtLogin, feishuBaseDomain, refreshOnStart
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
appToken = try c.decodeIfPresent(String.self, forKey: .appToken) ?? BundledDefault.appToken
|
||||||
|
tableId = try c.decodeIfPresent(String.self, forKey: .tableId) ?? BundledDefault.tableId
|
||||||
|
assigneeName = try c.decodeIfPresent(String.self, forKey: .assigneeName) ?? ""
|
||||||
|
fieldMappings = try c.decodeIfPresent(FieldMappings.self, forKey: .fieldMappings) ?? FieldMappings()
|
||||||
|
pollIntervalSeconds = try c.decodeIfPresent(Int.self, forKey: .pollIntervalSeconds) ?? 300
|
||||||
|
dailyRefreshTimes = try c.decodeIfPresent([String].self, forKey: .dailyRefreshTimes) ?? []
|
||||||
|
showFloatingWidget = try c.decodeIfPresent(Bool.self, forKey: .showFloatingWidget) ?? false
|
||||||
|
launchAtLogin = try c.decodeIfPresent(Bool.self, forKey: .launchAtLogin) ?? true
|
||||||
|
feishuBaseDomain = try c.decodeIfPresent(String.self, forKey: .feishuBaseDomain) ?? BundledDefault.feishuBaseDomain
|
||||||
|
refreshOnStart = try c.decodeIfPresent(Bool.self, forKey: .refreshOnStart) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Values baked into the app at build time via Config.xcconfig → Info.plist.
|
||||||
|
/// Used as defaults so internal users don't have to enter them in Settings.
|
||||||
|
enum BundledDefault {
|
||||||
|
static var appToken: String { string(for: "FEISHU_APP_TOKEN") }
|
||||||
|
static var tableId: String { string(for: "FEISHU_TABLE_ID") }
|
||||||
|
static var feishuBaseDomain: String { string(for: "FEISHU_BASE_DOMAIN") }
|
||||||
|
|
||||||
|
private static func string(for key: String) -> String {
|
||||||
|
let raw = (Bundle.main.object(forInfoDictionaryKey: key) as? String) ?? ""
|
||||||
|
// Xcode leaves an undefined $(...) substitution as the literal string;
|
||||||
|
// treat that as "not set" rather than showing "$(FEISHU_APP_TOKEN)".
|
||||||
|
if raw.hasPrefix("$(") { return "" }
|
||||||
|
return raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ struct Bug: Identifiable, Equatable, Hashable {
|
||||||
let customer: String?
|
let customer: String?
|
||||||
let createdAt: Date
|
let createdAt: Date
|
||||||
let updatedAt: Date
|
let updatedAt: Date
|
||||||
let feishuURL: URL
|
let feishuURL: URL?
|
||||||
|
|
||||||
var age: TimeInterval {
|
var age: TimeInterval {
|
||||||
Date().timeIntervalSince(createdAt)
|
Date().timeIntervalSince(createdAt)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ enum FeishuError: Error, LocalizedError {
|
||||||
case decodingError(Error, rawBody: String? = nil)
|
case decodingError(Error, rawBody: String? = nil)
|
||||||
case notConfigured
|
case notConfigured
|
||||||
case missingCredentials
|
case missingCredentials
|
||||||
|
case invalidConfiguration(String)
|
||||||
|
case paginationLimitExceeded
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
|
|
@ -25,6 +27,10 @@ enum FeishuError: Error, LocalizedError {
|
||||||
return "Feishu table not configured. Open Settings."
|
return "Feishu table not configured. Open Settings."
|
||||||
case .missingCredentials:
|
case .missingCredentials:
|
||||||
return "Feishu app credentials are missing. Set FEISHU_APP_ID and FEISHU_APP_SECRET."
|
return "Feishu app credentials are missing. Set FEISHU_APP_ID and FEISHU_APP_SECRET."
|
||||||
|
case .invalidConfiguration(let detail):
|
||||||
|
return "Invalid Feishu configuration: \(detail)"
|
||||||
|
case .paginationLimitExceeded:
|
||||||
|
return "Feishu table has too many records to load in one pass."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,16 @@ final class FeishuService {
|
||||||
) async throws -> [Bug] {
|
) async throws -> [Bug] {
|
||||||
var allRecords: [RecordItem] = []
|
var allRecords: [RecordItem] = []
|
||||||
var pageToken: String?
|
var pageToken: String?
|
||||||
|
var pages = 0
|
||||||
|
// 25,000 records at 500/page — guard against a runaway cursor that
|
||||||
|
// would otherwise loop forever accumulating memory.
|
||||||
|
let maxPages = 50
|
||||||
|
|
||||||
repeat {
|
repeat {
|
||||||
|
pages += 1
|
||||||
|
guard pages <= maxPages else {
|
||||||
|
throw FeishuError.paginationLimitExceeded
|
||||||
|
}
|
||||||
let page = try await fetchPage(
|
let page = try await fetchPage(
|
||||||
appToken: config.appToken,
|
appToken: config.appToken,
|
||||||
tableId: config.tableId,
|
tableId: config.tableId,
|
||||||
|
|
@ -60,16 +68,25 @@ final class FeishuService {
|
||||||
pageSize: Int = 500,
|
pageSize: Int = 500,
|
||||||
accessToken: String
|
accessToken: String
|
||||||
) async throws -> RecordListData {
|
) async throws -> RecordListData {
|
||||||
var components = URLComponents(
|
guard !appToken.isEmpty, !tableId.isEmpty else {
|
||||||
|
throw FeishuError.invalidConfiguration("App token and table ID must not be empty.")
|
||||||
|
}
|
||||||
|
guard var components = URLComponents(
|
||||||
string: "\(baseURL)/bitable/v1/apps/\(appToken)/tables/\(tableId)/records"
|
string: "\(baseURL)/bitable/v1/apps/\(appToken)/tables/\(tableId)/records"
|
||||||
)!
|
) else {
|
||||||
|
throw FeishuError.invalidConfiguration("App token or table ID contains invalid characters.")
|
||||||
|
}
|
||||||
var queryItems = [URLQueryItem(name: "page_size", value: String(pageSize))]
|
var queryItems = [URLQueryItem(name: "page_size", value: String(pageSize))]
|
||||||
if let pageToken {
|
if let pageToken {
|
||||||
queryItems.append(URLQueryItem(name: "page_token", value: pageToken))
|
queryItems.append(URLQueryItem(name: "page_token", value: pageToken))
|
||||||
}
|
}
|
||||||
components.queryItems = queryItems
|
components.queryItems = queryItems
|
||||||
|
|
||||||
var request = URLRequest(url: components.url!)
|
guard let url = components.url else {
|
||||||
|
throw FeishuError.invalidConfiguration("Could not build records request URL.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = URLRequest(url: url)
|
||||||
request.httpMethod = "GET"
|
request.httpMethod = "GET"
|
||||||
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
|
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
|
||||||
request.timeoutInterval = 30
|
request.timeoutInterval = 30
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,11 @@ final class NotificationService: NSObject, UNUserNotificationCenterDelegate {
|
||||||
guard isAuthorized else { return }
|
guard isAuthorized else { return }
|
||||||
|
|
||||||
let significant = changes
|
let significant = changes
|
||||||
.filter { $0.type == .newBug || $0.type == .priorityChanged }
|
.filter { change in
|
||||||
|
guard change.type == .newBug || change.type == .priorityChanged else { return false }
|
||||||
|
// Skip activity toasts for bugs that are already done.
|
||||||
|
return change.bug.status != .resolved && change.bug.status != .closed
|
||||||
|
}
|
||||||
.prefix(3)
|
.prefix(3)
|
||||||
|
|
||||||
for change in significant {
|
for change in significant {
|
||||||
|
|
@ -56,10 +60,11 @@ final class NotificationService: NSObject, UNUserNotificationCenterDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content.userInfo = [
|
var userInfo: [String: Any] = ["bugId": change.bug.id]
|
||||||
"bugId": change.bug.id,
|
if let url = change.bug.feishuURL {
|
||||||
"feishuURL": change.bug.feishuURL.absoluteString
|
userInfo["feishuURL"] = url.absoluteString
|
||||||
]
|
}
|
||||||
|
content.userInfo = userInfo
|
||||||
|
|
||||||
let request = UNNotificationRequest(
|
let request = UNNotificationRequest(
|
||||||
identifier: "bugger-\(change.bug.id)-\(Date().timeIntervalSince1970)",
|
identifier: "bugger-\(change.bug.id)-\(Date().timeIntervalSince1970)",
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,16 @@ final class PollerService {
|
||||||
tokenManager.isAuthenticated else {
|
tokenManager.isAuthenticated else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
start(interval: TimeInterval(config.pollIntervalSeconds))
|
start(interval: TimeInterval(config.pollIntervalSeconds), fetchImmediately: config.refreshOnStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(interval: TimeInterval) {
|
func start(interval: TimeInterval, fetchImmediately: Bool = true) {
|
||||||
guard !isRunning else { return }
|
guard !isRunning else { return }
|
||||||
isRunning = true
|
isRunning = true
|
||||||
|
|
||||||
|
if fetchImmediately {
|
||||||
Task { await performFetch() }
|
Task { await performFetch() }
|
||||||
|
}
|
||||||
|
|
||||||
if interval < 0 {
|
if interval < 0 {
|
||||||
// Daily schedule mode: check every 60s, fire at configured times
|
// Daily schedule mode: check every 60s, fire at configured times
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,26 @@ enum FeishuURLBuilder {
|
||||||
appToken: String,
|
appToken: String,
|
||||||
tableId: String,
|
tableId: String,
|
||||||
recordId: String
|
recordId: String
|
||||||
) -> URL {
|
) -> URL? {
|
||||||
// Feishu Bitable record deep link: uses query parameters
|
// Feishu Bitable record deep link: uses query parameters.
|
||||||
URL(string: "https://\(baseDomain)/base/\(appToken)?table=\(tableId)&record=\(recordId)")!
|
// Returns nil for malformed inputs (e.g. config containing spaces or
|
||||||
|
// slashes) instead of force-unwrapping and crashing.
|
||||||
|
var components = URLComponents()
|
||||||
|
components.scheme = "https"
|
||||||
|
components.host = baseDomain
|
||||||
|
components.path = "/base/\(appToken)"
|
||||||
|
components.queryItems = [
|
||||||
|
URLQueryItem(name: "table", value: tableId),
|
||||||
|
URLQueryItem(name: "record", value: recordId)
|
||||||
|
]
|
||||||
|
return components.url
|
||||||
}
|
}
|
||||||
|
|
||||||
static func tableURL(baseDomain: String, appToken: String) -> URL {
|
static func tableURL(baseDomain: String, appToken: String) -> URL? {
|
||||||
URL(string: "https://\(baseDomain)/base/\(appToken)")!
|
var components = URLComponents()
|
||||||
|
components.scheme = "https"
|
||||||
|
components.host = baseDomain
|
||||||
|
components.path = "/base/\(appToken)"
|
||||||
|
return components.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@ import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class FloatingWidgetWindow: NSPanel {
|
final class FloatingWidgetWindow: NSPanel {
|
||||||
|
private let margin: CGFloat = 16
|
||||||
|
private let bottomOffset: CGFloat = 200
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(
|
super.init(
|
||||||
contentRect: NSRect(x: 0, y: 0, width: 140, height: 70),
|
contentRect: NSRect(x: 0, y: 0, width: 1, height: 1),
|
||||||
styleMask: [.borderless, .nonactivatingPanel],
|
styleMask: [.borderless, .nonactivatingPanel],
|
||||||
backing: .buffered,
|
backing: .buffered,
|
||||||
defer: false
|
defer: false
|
||||||
|
|
@ -20,20 +23,63 @@ final class FloatingWidgetWindow: NSPanel {
|
||||||
hidesOnDeactivate = false
|
hidesOnDeactivate = false
|
||||||
animationBehavior = .none
|
animationBehavior = .none
|
||||||
|
|
||||||
|
// Place the 1x1 placeholder in the bottom-right corner so the first
|
||||||
|
// content measurement anchors to the screen's right edge.
|
||||||
if let screen = NSScreen.main {
|
if let screen = NSScreen.main {
|
||||||
let screenFrame = screen.visibleFrame
|
let screenFrame = screen.visibleFrame
|
||||||
setFrameOrigin(NSPoint(x: screenFrame.maxX - 100, y: screenFrame.minY + 200))
|
setFrameOrigin(NSPoint(
|
||||||
|
x: screenFrame.maxX - 1 - margin,
|
||||||
|
y: screenFrame.minY + bottomOffset
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView = NSHostingView(rootView: FloatingWidgetView())
|
let rootView = FloatingWidgetView { [weak self] size in
|
||||||
|
self?.applyContentSize(size)
|
||||||
|
}
|
||||||
|
let hostingView = NSHostingView(rootView: rootView)
|
||||||
|
contentView = hostingView
|
||||||
|
|
||||||
|
// Size to the content's intrinsic size up front to avoid a 1x1 flash
|
||||||
|
// before the first SwiftUI layout pass reports a measurement.
|
||||||
|
applyContentSize(hostingView.fittingSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var canBecomeKey: Bool { false }
|
override var canBecomeKey: Bool { false }
|
||||||
override var canBecomeMain: Bool { false }
|
override var canBecomeMain: Bool { false }
|
||||||
|
|
||||||
|
/// Resizes the window to fit its content while keeping the bottom-right
|
||||||
|
/// corner anchored, so a growing bug count extends leftward instead of
|
||||||
|
/// spilling off-screen. Clamped to the visible screen frame as a safety
|
||||||
|
/// net (e.g. when the user has dragged the widget toward an edge).
|
||||||
|
private func applyContentSize(_ size: CGSize) {
|
||||||
|
guard size.width > 0, size.height > 0 else { return }
|
||||||
|
// Skip no-op resizes to avoid a layout feedback loop with SwiftUI.
|
||||||
|
guard abs(size.width - frame.width) > 0.5
|
||||||
|
|| abs(size.height - frame.height) > 0.5 else { return }
|
||||||
|
|
||||||
|
var newFrame = frame
|
||||||
|
// Keep the right edge: grow leftward as the count widens.
|
||||||
|
newFrame.origin.x = newFrame.maxX - size.width
|
||||||
|
// Keep the bottom edge: grow upward when the priority row appears.
|
||||||
|
newFrame.origin.y = newFrame.minY
|
||||||
|
newFrame.size = size
|
||||||
|
|
||||||
|
if let screen = NSScreen.main {
|
||||||
|
let visible = screen.visibleFrame
|
||||||
|
newFrame.origin.x = max(newFrame.origin.x, visible.minX)
|
||||||
|
newFrame.origin.y = min(
|
||||||
|
max(newFrame.origin.y, visible.minY),
|
||||||
|
visible.maxY - size.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrame(newFrame, display: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FloatingWidgetView: View {
|
struct FloatingWidgetView: View {
|
||||||
@State private var bugStore = BugStore.shared
|
@State private var bugStore = BugStore.shared
|
||||||
|
let onSizeChange: (CGSize) -> Void
|
||||||
|
|
||||||
private var priorityCounts: [(BugPriority, Int)] {
|
private var priorityCounts: [(BugPriority, Int)] {
|
||||||
let active = bugStore.activeBugs
|
let active = bugStore.activeBugs
|
||||||
|
|
@ -56,6 +102,7 @@ struct FloatingWidgetView: View {
|
||||||
Text("\(bugStore.activeBugs.count)")
|
Text("\(bugStore.activeBugs.count)")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
.monospacedDigit()
|
||||||
}
|
}
|
||||||
if !priorityCounts.isEmpty {
|
if !priorityCounts.isEmpty {
|
||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
|
|
@ -67,6 +114,7 @@ struct FloatingWidgetView: View {
|
||||||
Text("\(count)")
|
Text("\(count)")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
|
.monospacedDigit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,5 +129,17 @@ struct FloatingWidgetView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
// Let the button take its intrinsic size so the window can fit it
|
||||||
|
// exactly, regardless of the (initially 1x1) hosting container.
|
||||||
|
.fixedSize()
|
||||||
|
.background(
|
||||||
|
GeometryReader { proxy in
|
||||||
|
Color.clear
|
||||||
|
.onAppear { onSizeChange(proxy.size) }
|
||||||
|
.onChange(of: proxy.size) { _, newSize in
|
||||||
|
onSizeChange(newSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import SwiftUI
|
||||||
struct BugListPopover: View {
|
struct BugListPopover: View {
|
||||||
@State private var bugStore = BugStore.shared
|
@State private var bugStore = BugStore.shared
|
||||||
@State private var tokenManager = TokenManager.shared
|
@State private var tokenManager = TokenManager.shared
|
||||||
|
@Environment(\.openSettings) private var openSettings
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
|
@ -90,7 +91,16 @@ struct BugListPopover: View {
|
||||||
Button("Refresh") {
|
Button("Refresh") {
|
||||||
Task { await PollerService.shared.fetchNow() }
|
Task { await PollerService.shared.fetchNow() }
|
||||||
}
|
}
|
||||||
SettingsLink {
|
Button {
|
||||||
|
openSettings()
|
||||||
|
// Bugger runs as an accessory (no Dock icon), so the Settings
|
||||||
|
// window would otherwise open behind whatever is frontmost.
|
||||||
|
// Activate on the next runloop pass, once the window exists,
|
||||||
|
// to bring it on top.
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NSRunningApplication.current.activate(options: [.activateAllWindows])
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
Label("Settings", systemImage: "gear")
|
Label("Settings", systemImage: "gear")
|
||||||
}
|
}
|
||||||
Button(action: { NSApp.terminate(nil) }) {
|
Button(action: { NSApp.terminate(nil) }) {
|
||||||
|
|
@ -104,16 +114,17 @@ struct BugListPopover: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openInFeishu(_ bug: Bug) {
|
private func openInFeishu(_ bug: Bug) {
|
||||||
NSWorkspace.shared.open(bug.feishuURL)
|
guard let url = bug.feishuURL else { return }
|
||||||
|
NSWorkspace.shared.open(url)
|
||||||
AppDelegate.shared?.closePopover()
|
AppDelegate.shared?.closePopover()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openFeishuTable() {
|
private func openFeishuTable() {
|
||||||
guard let config = AppStateService.shared.config else { return }
|
guard let config = AppStateService.shared.config,
|
||||||
let url = FeishuURLBuilder.tableURL(
|
let url = FeishuURLBuilder.tableURL(
|
||||||
baseDomain: config.feishuBaseDomain,
|
baseDomain: config.feishuBaseDomain,
|
||||||
appToken: config.appToken
|
appToken: config.appToken
|
||||||
)
|
) else { return }
|
||||||
NSWorkspace.shared.open(url)
|
NSWorkspace.shared.open(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +176,11 @@ struct ErrorStateView: View {
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
Button("Retry") {
|
||||||
|
Task { await PollerService.shared.fetchNow() }
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.padding(.top, 4)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.padding()
|
.padding()
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,29 @@ struct SettingsView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
// Highlighted "Start here" card lives outside the Form so its
|
||||||
|
// Spacer + multi-element HStack don't disturb the Form's
|
||||||
|
// two-column label/control layout (which was pushing the
|
||||||
|
// Feishu Bitable fields off the right edge of the window).
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Image(systemName: "person.fill")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundStyle(.tint)
|
||||||
|
Text("Your Name")
|
||||||
|
.font(.headline)
|
||||||
|
TextField("e.g. Alice", text: $config.assigneeName)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(10)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8).fill(.tint.opacity(0.08)))
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(.tint.opacity(0.35), lineWidth: 1))
|
||||||
|
|
||||||
Form {
|
Form {
|
||||||
Section("Feishu Bitable") {
|
Section("Feishu Bitable") {
|
||||||
TextField("App Token (from Bitable URL)", text: $config.appToken)
|
TextField("App Token (from Bitable URL)", text: $config.appToken)
|
||||||
TextField("Table ID", text: $config.tableId)
|
TextField("Table ID", text: $config.tableId)
|
||||||
TextField("Your Name (as in Assignee column, optional)", text: $config.assigneeName)
|
|
||||||
TextField("Feishu domain", text: $config.feishuBaseDomain)
|
TextField("Feishu domain", text: $config.feishuBaseDomain)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
|
@ -86,6 +104,13 @@ struct SettingsView: View {
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Toggle("Refresh on launch", isOn: $config.refreshOnStart)
|
||||||
|
Text("Fetch bugs immediately when Bugger starts. Off = wait for the next scheduled poll.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Display") {
|
Section("Display") {
|
||||||
|
|
@ -100,7 +125,13 @@ struct SettingsView: View {
|
||||||
Toggle("Launch at login", isOn: $config.launchAtLogin)
|
Toggle("Launch at login", isOn: $config.launchAtLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Field Mappings") {
|
Section {
|
||||||
|
DisclosureGroup(isExpanded: $showAdvanced) {
|
||||||
|
Text("Field Mappings")
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
TextField("Title field", text: $config.fieldMappings.titleField)
|
TextField("Title field", text: $config.fieldMappings.titleField)
|
||||||
TextField("Priority field", text: $config.fieldMappings.priorityField)
|
TextField("Priority field", text: $config.fieldMappings.priorityField)
|
||||||
TextField("Status field", text: $config.fieldMappings.statusField)
|
TextField("Status field", text: $config.fieldMappings.statusField)
|
||||||
|
|
@ -109,9 +140,11 @@ struct SettingsView: View {
|
||||||
TextField("Customer field", text: $config.fieldMappings.customerField)
|
TextField("Customer field", text: $config.fieldMappings.customerField)
|
||||||
TextField("Created At field", text: $config.fieldMappings.createdAtField)
|
TextField("Created At field", text: $config.fieldMappings.createdAtField)
|
||||||
TextField("Updated At field", text: $config.fieldMappings.updatedAtField)
|
TextField("Updated At field", text: $config.fieldMappings.updatedAtField)
|
||||||
}
|
|
||||||
|
|
||||||
Section("Status Mappings") {
|
Text("Status Mappings")
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
Text("Map your Feishu status values to standard Bugger statuses.")
|
Text("Map your Feishu status values to standard Bugger statuses.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
@ -119,6 +152,9 @@ struct SettingsView: View {
|
||||||
ForEach(BugStatus.allCases.filter { $0 != .unknown }, id: \.self) { status in
|
ForEach(BugStatus.allCases.filter { $0 != .unknown }, id: \.self) { status in
|
||||||
statusMappingRow(for: status)
|
statusMappingRow(for: status)
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Advanced: field & status mappings", systemImage: "slider.horizontal.3")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -133,7 +169,8 @@ struct SettingsView: View {
|
||||||
}
|
}
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
}
|
}
|
||||||
}
|
} // Form
|
||||||
|
} // VStack
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
.frame(minWidth: 500, idealWidth: 520, minHeight: 400, maxHeight: 800)
|
.frame(minWidth: 500, idealWidth: 520, minHeight: 400, maxHeight: 800)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
PROJECT="Bugger.xcodeproj"
|
PROJECT="Bugger.xcodeproj"
|
||||||
SCHEME="Bugger"
|
SCHEME="Bugger"
|
||||||
VERSION="1.1"
|
|
||||||
BUILD_DIR="build"
|
BUILD_DIR="build"
|
||||||
APP_NAME="Bugger.app"
|
APP_NAME="Bugger.app"
|
||||||
|
|
||||||
|
|
@ -36,8 +35,16 @@ if [ -z "$APP_PATH" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Read the marketing version from the built app so the DMG name stays in
|
||||||
|
# sync with MARKETING_VERSION / CFBundleShortVersionString automatically.
|
||||||
|
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PATH/Contents/Info.plist")
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "ERROR: Could not read version from $APP_PATH/Contents/Info.plist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Built: $APP_PATH ==="
|
echo "=== Built: $APP_PATH (v$VERSION) ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Copy to build root
|
# Copy to build root
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue