mirror of
https://github.com/Finb/Bark.git
synced 2025-12-08 21:36:01 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
ffd205d3e1
6
.github/workflows/testflight.yaml
vendored
6
.github/workflows/testflight.yaml
vendored
@ -14,7 +14,7 @@ on:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
- name: Select Xcode Version
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '13.2'
|
||||
xcode-version: 'latest'
|
||||
|
||||
- name: Setup ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
@ -52,4 +52,4 @@ jobs:
|
||||
APP_STORE_CONNECT_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_KEY_CONTENT }}
|
||||
BARK_KEY: ${{ secrets.BARK_KEY }}
|
||||
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
|
||||
4
.github/workflows/tests.yaml
vendored
4
.github/workflows/tests.yaml
vendored
@ -10,7 +10,7 @@ on:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -19,7 +19,7 @@ jobs:
|
||||
- name: Select Xcode Version
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '13.2'
|
||||
xcode-version: '14.0.1'
|
||||
|
||||
- name: Setup ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
@ -14,8 +14,14 @@
|
||||
060481EE250F404500BC9799 /* SoundsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060481ED250F404500BC9799 /* SoundsViewController.swift */; };
|
||||
060481F0250F51CA00BC9799 /* SoundCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060481EF250F51CA00BC9799 /* SoundCell.swift */; };
|
||||
0604F7DF20620D4900B32F09 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0604F7DE20620D4900B32F09 /* ServerManager.swift */; };
|
||||
0608F06E2994D115006B8029 /* BKDropDownCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0608F06D2994D115006B8029 /* BKDropDownCell.swift */; };
|
||||
0608F0722994D269006B8029 /* BKDropDownCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0608F0712994D269006B8029 /* BKDropDownCell.xib */; };
|
||||
06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */; };
|
||||
06172FDC27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */; };
|
||||
061894C529962EB900E001C2 /* GradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C429962EB900E001C2 /* GradientButton.swift */; };
|
||||
061894C729A75BEA00E001C2 /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C629A75BEA00E001C2 /* Algorithm.swift */; };
|
||||
0627DABB298B6EA2002F3F69 /* DropBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0627DABA298B6EA2002F3F69 /* DropBoxView.swift */; };
|
||||
0627DABD2990D615002F3F69 /* BorderTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0627DABC2990D615002F3F69 /* BorderTextField.swift */; };
|
||||
062B98C3251B2762004562E7 /* BKButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B98C2251B2762004562E7 /* BKButton.swift */; };
|
||||
062B98C8251B27AE004562E7 /* UINavigationItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */; };
|
||||
0632050F250B6DD4001561EC /* gotosleep.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F0250B6DD1001561EC /* gotosleep.caf */; };
|
||||
@ -70,15 +76,15 @@
|
||||
0642B55C27EB149900453D91 /* MutableTextCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */; };
|
||||
064CAB9E256BE9090018155C /* PreviewCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064CAB9D256BE9090018155C /* PreviewCardCellViewModel.swift */; };
|
||||
064CABA6256BE9510018155C /* PreviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064CABA5256BE9510018155C /* PreviewModel.swift */; };
|
||||
0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */; };
|
||||
0653677829B727A60038BDB8 /* CryptoSettingRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */; };
|
||||
065A4D4220EE1A31002EB2DB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 063C499720E36BF9001BCA35 /* Localizable.strings */; };
|
||||
065AE76B2987777F00323230 /* ArchiveSettingRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065AE76A2987777F00323230 /* ArchiveSettingRelay.swift */; };
|
||||
065BE4402563D649002A8CA4 /* SoundsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE43F2563D649002A8CA4 /* SoundsViewModel.swift */; };
|
||||
065BE4462563D7E5002A8CA4 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE4452563D7E5002A8CA4 /* ViewModelType.swift */; };
|
||||
065BE44B2563D8E1002A8CA4 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE44A2563D8E1002A8CA4 /* Reusable.swift */; };
|
||||
065BE4502563D939002A8CA4 /* SoundCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE44F2563D939002A8CA4 /* SoundCellViewModel.swift */; };
|
||||
065BE4552565055F002A8CA4 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE4542565055F002A8CA4 /* HomeViewModel.swift */; };
|
||||
0660480B2700550600938904 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0660480A2700550600938904 /* Intents.framework */; };
|
||||
0660480E2700550600938904 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0660480D2700550600938904 /* IntentHandler.swift */; };
|
||||
066048122700550600938904 /* Intent.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 066048092700550600938904 /* Intent.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0661A543204FDA4100965E4E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0661A542204FDA4100965E4E /* AppDelegate.swift */; };
|
||||
0661A545204FDA4100965E4E /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0661A544204FDA4100965E4E /* HomeViewController.swift */; };
|
||||
0661A54A204FDA4100965E4E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0661A549204FDA4100965E4E /* Assets.xcassets */; };
|
||||
@ -86,6 +92,7 @@
|
||||
0667D192247D162C005DE2ED /* MessageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0667D191247D162C005DE2ED /* MessageTableViewCell.swift */; };
|
||||
0667D194247D1BA0005DE2ED /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0667D193247D1BA0005DE2ED /* Date+Extension.swift */; };
|
||||
0672CB06256903F700570C9D /* MessageListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0672CB05256903F700570C9D /* MessageListViewModel.swift */; };
|
||||
06787C392A710568008ABDD7 /* GesturePassTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06787C382A710568008ABDD7 /* GesturePassTextView.swift */; };
|
||||
067B2EB525693E38008B6BE1 /* MessageTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */; };
|
||||
06802E5320ECC40C00767047 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0661A549204FDA4100965E4E /* Assets.xcassets */; };
|
||||
06840DBB272298FB001B3193 /* BKColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06840DBA272298FB001B3193 /* BKColor.swift */; };
|
||||
@ -108,6 +115,7 @@
|
||||
06BBB8C12567B3EF0076F63E /* BaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BBB8C02567B3EF0076F63E /* BaseTableViewCell.swift */; };
|
||||
06BBB8C92567B6730076F63E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BBB8C82567B6730076F63E /* Operators.swift */; };
|
||||
06BBB8CE2567B8E60076F63E /* silence.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06BBB8CD2567B8E60076F63E /* silence.caf */; };
|
||||
06BD4DAA2901352E003364DB /* Object+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BD4DA92901352E003364DB /* Object+Dictionary.swift */; };
|
||||
06C2CF232685B88D0034B127 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C2CF222685B88D0034B127 /* TextCell.swift */; };
|
||||
06C2CF252685BDB80034B127 /* SpacerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C2CF242685BDB80034B127 /* SpacerCell.swift */; };
|
||||
06C5952D2480E3F8006B98F3 /* LabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C5952C2480E3F8006B98F3 /* LabelCell.swift */; };
|
||||
@ -117,6 +125,16 @@
|
||||
06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CF784B21C7A51200A052D7 /* NotificationService.swift */; };
|
||||
06EE1FD326843E9300586708 /* BarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EE1FD226843E9300586708 /* BarkTests.swift */; };
|
||||
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF332291CCFF400CA228A /* CryptoSettingController.swift */; };
|
||||
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */; };
|
||||
06F08EA429B098DD006AB9CA /* CryptoSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */; };
|
||||
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C629A75BEA00E001C2 /* Algorithm.swift */; };
|
||||
06F08EA729B1DDFE006AB9CA /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */; };
|
||||
06F08EA829B1DE0A006AB9CA /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */; };
|
||||
06F08EAA29B1DE9F006AB9CA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 063C499720E36BF9001BCA35 /* Localizable.strings */; };
|
||||
06F08EAC29B1DECD006AB9CA /* NSLocalizedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */; };
|
||||
06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */; };
|
||||
06F08EAF29B5D9FF006AB9CA /* HUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAE29B5D9FF006AB9CA /* HUD.swift */; };
|
||||
06F11E7727D9D5FB00F00298 /* QRScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */; };
|
||||
3428272069AFAFE2C683FEB0 /* libPods-Bark.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC722470308049D180876C7 /* libPods-Bark.a */; };
|
||||
879AE4D4178855A9672009E4 /* libPods-NotificationServiceExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B7F8BDFAA047451561798F58 /* libPods-NotificationServiceExtension.a */; };
|
||||
@ -131,13 +149,6 @@
|
||||
remoteGlobalIDString = 0632CE1D20EC9098003FDF46;
|
||||
remoteInfo = NotificationContentExtension;
|
||||
};
|
||||
066048102700550600938904 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0661A537204FDA4100965E4E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 066048082700550600938904;
|
||||
remoteInfo = Intent;
|
||||
};
|
||||
06CF784521C7A50300A052D7 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0661A537204FDA4100965E4E /* Project object */;
|
||||
@ -162,7 +173,6 @@
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
0632CE2A20EC9098003FDF46 /* NotificationContentExtension.appex in Embed App Extensions */,
|
||||
066048122700550600938904 /* Intent.appex in Embed App Extensions */,
|
||||
06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
@ -178,8 +188,14 @@
|
||||
060481ED250F404500BC9799 /* SoundsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundsViewController.swift; sourceTree = "<group>"; };
|
||||
060481EF250F51CA00BC9799 /* SoundCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundCell.swift; sourceTree = "<group>"; };
|
||||
0604F7DE20620D4900B32F09 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = "<group>"; };
|
||||
0608F06D2994D115006B8029 /* BKDropDownCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BKDropDownCell.swift; sourceTree = "<group>"; };
|
||||
0608F0712994D269006B8029 /* BKDropDownCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BKDropDownCell.xib; sourceTree = "<group>"; };
|
||||
06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableViewCell.swift; sourceTree = "<group>"; };
|
||||
06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableViewCellViewModel.swift; sourceTree = "<group>"; };
|
||||
061894C429962EB900E001C2 /* GradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButton.swift; sourceTree = "<group>"; };
|
||||
061894C629A75BEA00E001C2 /* Algorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Algorithm.swift; sourceTree = "<group>"; };
|
||||
0627DABA298B6EA2002F3F69 /* DropBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropBoxView.swift; sourceTree = "<group>"; };
|
||||
0627DABC2990D615002F3F69 /* BorderTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderTextField.swift; sourceTree = "<group>"; };
|
||||
062B98C2251B2762004562E7 /* BKButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BKButton.swift; sourceTree = "<group>"; };
|
||||
062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationItem+Extension.swift"; sourceTree = "<group>"; };
|
||||
063204F0250B6DD1001561EC /* gotosleep.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = gotosleep.caf; sourceTree = "<group>"; };
|
||||
@ -234,15 +250,14 @@
|
||||
0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableTextCellViewModel.swift; sourceTree = "<group>"; };
|
||||
064CAB9D256BE9090018155C /* PreviewCardCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCardCellViewModel.swift; sourceTree = "<group>"; };
|
||||
064CABA5256BE9510018155C /* PreviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewModel.swift; sourceTree = "<group>"; };
|
||||
0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingRelay.swift; sourceTree = "<group>"; };
|
||||
065AE76A2987777F00323230 /* ArchiveSettingRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveSettingRelay.swift; sourceTree = "<group>"; };
|
||||
065BE43F2563D649002A8CA4 /* SoundsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundsViewModel.swift; sourceTree = "<group>"; };
|
||||
065BE4452563D7E5002A8CA4 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = "<group>"; };
|
||||
065BE44A2563D8E1002A8CA4 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = "<group>"; };
|
||||
065BE44F2563D939002A8CA4 /* SoundCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundCellViewModel.swift; sourceTree = "<group>"; };
|
||||
065BE4542565055F002A8CA4 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
|
||||
066048092700550600938904 /* Intent.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Intent.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0660480A2700550600938904 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
|
||||
0660480D2700550600938904 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = "<group>"; };
|
||||
0660480F2700550600938904 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0661A53F204FDA4100965E4E /* Bark.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bark.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0661A542204FDA4100965E4E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
0661A544204FDA4100965E4E /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
||||
@ -252,6 +267,7 @@
|
||||
0667D191247D162C005DE2ED /* MessageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewCell.swift; sourceTree = "<group>"; };
|
||||
0667D193247D1BA0005DE2ED /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = "<group>"; };
|
||||
0672CB05256903F700570C9D /* MessageListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewModel.swift; sourceTree = "<group>"; };
|
||||
06787C382A710568008ABDD7 /* GesturePassTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GesturePassTextView.swift; sourceTree = "<group>"; };
|
||||
067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewCellViewModel.swift; sourceTree = "<group>"; };
|
||||
0683486A2050F1310024B6DA /* Bark.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bark.entitlements; sourceTree = "<group>"; };
|
||||
0683487020510FB20024B6DA /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
|
||||
@ -274,6 +290,7 @@
|
||||
06BBB8C02567B3EF0076F63E /* BaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTableViewCell.swift; sourceTree = "<group>"; };
|
||||
06BBB8C82567B6730076F63E /* Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
|
||||
06BBB8CD2567B8E60076F63E /* silence.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = silence.caf; sourceTree = "<group>"; };
|
||||
06BD4DA92901352E003364DB /* Object+Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Object+Dictionary.swift"; sourceTree = "<group>"; };
|
||||
06C2CF222685B88D0034B127 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = "<group>"; };
|
||||
06C2CF242685BDB80034B127 /* SpacerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerCell.swift; sourceTree = "<group>"; };
|
||||
06C5952C2480E3F8006B98F3 /* LabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCell.swift; sourceTree = "<group>"; };
|
||||
@ -287,6 +304,12 @@
|
||||
06EE1FD026843E9300586708 /* BarkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BarkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
06EE1FD226843E9300586708 /* BarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarkTests.swift; sourceTree = "<group>"; };
|
||||
06EE1FD426843E9300586708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
06EEF332291CCFF400CA228A /* CryptoSettingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingController.swift; sourceTree = "<group>"; };
|
||||
06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingViewModel.swift; sourceTree = "<group>"; };
|
||||
06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingManager.swift; sourceTree = "<group>"; };
|
||||
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Extension.swift"; sourceTree = "<group>"; };
|
||||
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocalizedString+Extension.swift"; sourceTree = "<group>"; };
|
||||
06F08EAE29B5D9FF006AB9CA /* HUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = "<group>"; };
|
||||
06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerViewController.swift; sourceTree = "<group>"; };
|
||||
121D9B1ED4E8D26F345BC5C0 /* Pods-BarkTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BarkTests.release.xcconfig"; path = "Target Support Files/Pods-BarkTests/Pods-BarkTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
138CE8CB688587E893BC5C44 /* Pods-Bark.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bark.debug.xcconfig"; path = "Target Support Files/Pods-Bark/Pods-Bark.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
@ -295,6 +318,7 @@
|
||||
A69B47DA6DB3B168D5770B45 /* Pods-Bark.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bark.release.xcconfig"; path = "Target Support Files/Pods-Bark/Pods-Bark.release.xcconfig"; sourceTree = "<group>"; };
|
||||
B7F8BDFAA047451561798F58 /* libPods-NotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CCC722470308049D180876C7 /* libPods-Bark.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Bark.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E3B5CBEB2A94AC26006E25F9 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F20815A821395CCA155806A4 /* Pods-NotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FB59D77AB30F7AD98BA72C3E /* Pods-BarkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BarkTests.debug.xcconfig"; path = "Target Support Files/Pods-BarkTests/Pods-BarkTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -309,14 +333,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
066048062700550600938904 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0660480B2700550600938904 /* Intents.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0661A53C204FDA4100965E4E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -366,6 +382,8 @@
|
||||
06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */,
|
||||
068EC15727ED99C900D5D11E /* ServerListViewController.swift */,
|
||||
068EC15927ED99E700D5D11E /* ServerListViewModel.swift */,
|
||||
06EEF332291CCFF400CA228A /* CryptoSettingController.swift */,
|
||||
06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */,
|
||||
);
|
||||
path = Controller;
|
||||
sourceTree = "<group>";
|
||||
@ -395,6 +413,13 @@
|
||||
0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */,
|
||||
06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */,
|
||||
06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */,
|
||||
0627DABA298B6EA2002F3F69 /* DropBoxView.swift */,
|
||||
0627DABC2990D615002F3F69 /* BorderTextField.swift */,
|
||||
0608F06D2994D115006B8029 /* BKDropDownCell.swift */,
|
||||
0608F0712994D269006B8029 /* BKDropDownCell.xib */,
|
||||
061894C429962EB900E001C2 /* GradientButton.swift */,
|
||||
06F08EAE29B5D9FF006AB9CA /* HUD.swift */,
|
||||
06787C382A710568008ABDD7 /* GesturePassTextView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@ -404,6 +429,8 @@
|
||||
children = (
|
||||
06B1158E247BB1FB006D91FB /* Message.swift */,
|
||||
064CABA5256BE9510018155C /* PreviewModel.swift */,
|
||||
06BD4DA92901352E003364DB /* Object+Dictionary.swift */,
|
||||
061894C629A75BEA00E001C2 /* Algorithm.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -474,6 +501,11 @@
|
||||
06BBB8C82567B6730076F63E /* Operators.swift */,
|
||||
0633E809256A091B00ED0680 /* MJRefresh+Rx.swift */,
|
||||
06840DBA272298FB001B3193 /* BKColor.swift */,
|
||||
065AE76A2987777F00323230 /* ArchiveSettingRelay.swift */,
|
||||
06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */,
|
||||
0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */,
|
||||
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */,
|
||||
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */,
|
||||
);
|
||||
path = Common;
|
||||
sourceTree = "<group>";
|
||||
@ -488,15 +520,6 @@
|
||||
path = Moya;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0660480C2700550600938904 /* Intent */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0660480D2700550600938904 /* IntentHandler.swift */,
|
||||
0660480F2700550600938904 /* Info.plist */,
|
||||
);
|
||||
path = Intent;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0661A536204FDA4100965E4E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -509,7 +532,6 @@
|
||||
0632CE2120EC9098003FDF46 /* NotificationContentExtension */,
|
||||
06CF784121C7A50300A052D7 /* NotificationServiceExtension */,
|
||||
06EE1FD126843E9300586708 /* BarkTests */,
|
||||
0660480C2700550600938904 /* Intent */,
|
||||
0661A540204FDA4100965E4E /* Products */,
|
||||
99BD309BDB7F62B5DC0CECE1 /* Frameworks */,
|
||||
E9062CB15F1B70C3719578FB /* Pods */,
|
||||
@ -523,7 +545,6 @@
|
||||
0632CE1E20EC9098003FDF46 /* NotificationContentExtension.appex */,
|
||||
06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */,
|
||||
06EE1FD026843E9300586708 /* BarkTests.xctest */,
|
||||
066048092700550600938904 /* Intent.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -608,23 +629,6 @@
|
||||
productReference = 0632CE1E20EC9098003FDF46 /* NotificationContentExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
066048082700550600938904 /* Intent */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 066048132700550600938904 /* Build configuration list for PBXNativeTarget "Intent" */;
|
||||
buildPhases = (
|
||||
066048052700550600938904 /* Sources */,
|
||||
066048062700550600938904 /* Frameworks */,
|
||||
066048072700550600938904 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Intent;
|
||||
productName = Intent;
|
||||
productReference = 066048092700550600938904 /* Intent.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
0661A53E204FDA4100965E4E /* Bark */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0661A551204FDA4100965E4E /* Build configuration list for PBXNativeTarget "Bark" */;
|
||||
@ -641,7 +645,6 @@
|
||||
dependencies = (
|
||||
0632CE2920EC9098003FDF46 /* PBXTargetDependency */,
|
||||
06CF784621C7A50300A052D7 /* PBXTargetDependency */,
|
||||
066048112700550600938904 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Bark;
|
||||
productName = Bark;
|
||||
@ -699,10 +702,6 @@
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
066048082700550600938904 = {
|
||||
CreatedOnToolsVersion = 13.0;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
0661A53E204FDA4100965E4E = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Manual;
|
||||
@ -731,6 +730,7 @@
|
||||
en,
|
||||
Base,
|
||||
"zh-Hans",
|
||||
tr,
|
||||
);
|
||||
mainGroup = 0661A536204FDA4100965E4E;
|
||||
productRefGroup = 0661A540204FDA4100965E4E /* Products */;
|
||||
@ -741,7 +741,6 @@
|
||||
0632CE1D20EC9098003FDF46 /* NotificationContentExtension */,
|
||||
06CF783F21C7A50300A052D7 /* NotificationServiceExtension */,
|
||||
06EE1FCF26843E9300586708 /* BarkTests */,
|
||||
066048082700550600938904 /* Intent */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -757,13 +756,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
066048072700550600938904 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0661A53D204FDA4100965E4E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -789,6 +781,7 @@
|
||||
0661A54D204FDA4100965E4E /* LaunchScreen.storyboard in Resources */,
|
||||
0632051E250B6DD4001561EC /* choo.caf in Resources */,
|
||||
06320516250B6DD4001561EC /* calypso.caf in Resources */,
|
||||
0608F0722994D269006B8029 /* BKDropDownCell.xib in Resources */,
|
||||
0632052D250B6DD4001561EC /* newsflash.caf in Resources */,
|
||||
06320514250B6DD4001561EC /* sherwoodforest.caf in Resources */,
|
||||
0632051A250B6DD4001561EC /* fanfare.caf in Resources */,
|
||||
@ -810,6 +803,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
06F08EAA29B1DE9F006AB9CA /* Localizable.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -852,6 +846,7 @@
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Bark/Pods-Bark-resources.sh",
|
||||
"${PODS_ROOT}/DropDown/DropDown/resources/DropDownCell.xib",
|
||||
"${PODS_ROOT}/MJRefresh/MJRefresh/MJRefresh.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/Material/com.cosmicmind.material.icons.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/Material/com.cosmicmind.material.fonts.bundle",
|
||||
@ -860,6 +855,7 @@
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DropDownCell.nib",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MJRefresh.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/com.cosmicmind.material.icons.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/com.cosmicmind.material.fonts.bundle",
|
||||
@ -926,14 +922,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
066048052700550600938904 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0660480E2700550600938904 /* IntentHandler.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0661A53B204FDA4100965E4E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -943,21 +931,30 @@
|
||||
06BBB896256518760076F63E /* NewServerViewModel.swift in Sources */,
|
||||
06BBB8C92567B6730076F63E /* Operators.swift in Sources */,
|
||||
0603706920E1F89500F4CA05 /* PreviewCardCell.swift in Sources */,
|
||||
0627DABB298B6EA2002F3F69 /* DropBoxView.swift in Sources */,
|
||||
06AE311C266F54A500B39FBB /* GroupTableViewCell.swift in Sources */,
|
||||
0672CB06256903F700570C9D /* MessageListViewModel.swift in Sources */,
|
||||
0627DABD2990D615002F3F69 /* BorderTextField.swift in Sources */,
|
||||
0633E80A256A091B00ED0680 /* MJRefresh+Rx.swift in Sources */,
|
||||
0637FA8C20E0D7A700E80174 /* BaseViewController.swift in Sources */,
|
||||
0642B55C27EB149900453D91 /* MutableTextCellViewModel.swift in Sources */,
|
||||
062B98C8251B27AE004562E7 /* UINavigationItem+Extension.swift in Sources */,
|
||||
060481EE250F404500BC9799 /* SoundsViewController.swift in Sources */,
|
||||
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */,
|
||||
0603706D20E23EC000F4CA05 /* BarkSFSafariViewController.swift in Sources */,
|
||||
06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */,
|
||||
06F08EA429B098DD006AB9CA /* CryptoSettingManager.swift in Sources */,
|
||||
06BD4DAA2901352E003364DB /* Object+Dictionary.swift in Sources */,
|
||||
06C5953124811392006B98F3 /* ArchiveSettingCell.swift in Sources */,
|
||||
068EC15A27ED99E700D5D11E /* ServerListViewModel.swift in Sources */,
|
||||
06172FDC27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift in Sources */,
|
||||
061894C729A75BEA00E001C2 /* Algorithm.swift in Sources */,
|
||||
065BE4502563D939002A8CA4 /* SoundCellViewModel.swift in Sources */,
|
||||
06F08EA729B1DDFE006AB9CA /* Error+Extension.swift in Sources */,
|
||||
06B1158F247BB1FB006D91FB /* Message.swift in Sources */,
|
||||
06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */,
|
||||
0653677829B727A60038BDB8 /* CryptoSettingRelay.swift in Sources */,
|
||||
061894C529962EB900E001C2 /* GradientButton.swift in Sources */,
|
||||
06C595362481160F006B98F3 /* BKLabel.swift in Sources */,
|
||||
0637FA7820E0926D00E80174 /* BarkTargetType.swift in Sources */,
|
||||
064CABA6256BE9510018155C /* PreviewModel.swift in Sources */,
|
||||
@ -983,12 +980,15 @@
|
||||
062B98C3251B2762004562E7 /* BKButton.swift in Sources */,
|
||||
06885EB6247FB9880004A303 /* MessageSettingsViewController.swift in Sources */,
|
||||
06F11E7727D9D5FB00F00298 /* QRScannerViewController.swift in Sources */,
|
||||
0608F06E2994D115006B8029 /* BKDropDownCell.swift in Sources */,
|
||||
06C5952D2480E3F8006B98F3 /* LabelCell.swift in Sources */,
|
||||
06787C392A710568008ABDD7 /* GesturePassTextView.swift in Sources */,
|
||||
068EC15827ED99C900D5D11E /* ServerListViewController.swift in Sources */,
|
||||
0637FA7C20E0930E00E80174 /* BarkApi.swift in Sources */,
|
||||
06C2CF252685BDB80034B127 /* SpacerCell.swift in Sources */,
|
||||
06BBB8BC2567B3AD0076F63E /* ArchiveSettingCellViewModel.swift in Sources */,
|
||||
0642B55A27EB13F100453D91 /* MutableTextCell.swift in Sources */,
|
||||
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */,
|
||||
0637FA8020E0981E00E80174 /* BarkSettings.swift in Sources */,
|
||||
065BE4402563D649002A8CA4 /* SoundsViewModel.swift in Sources */,
|
||||
065BE4462563D7E5002A8CA4 /* ViewModelType.swift in Sources */,
|
||||
@ -997,6 +997,9 @@
|
||||
06BBB8B72567AC140076F63E /* MessageSettingsViewModel.swift in Sources */,
|
||||
06BBB8C12567B3EF0076F63E /* BaseTableViewCell.swift in Sources */,
|
||||
0661A543204FDA4100965E4E /* AppDelegate.swift in Sources */,
|
||||
06F08EAF29B5D9FF006AB9CA /* HUD.swift in Sources */,
|
||||
065AE76B2987777F00323230 /* ArchiveSettingRelay.swift in Sources */,
|
||||
06F08EAC29B1DECD006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1005,8 +1008,12 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */,
|
||||
06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
|
||||
0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */,
|
||||
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */,
|
||||
06BBB89125650CCF0076F63E /* ArchiveSettingManager.swift in Sources */,
|
||||
06B11591247BC132006D91FB /* Message.swift in Sources */,
|
||||
06F08EA829B1DE0A006AB9CA /* Error+Extension.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1027,11 +1034,6 @@
|
||||
target = 0632CE1D20EC9098003FDF46 /* NotificationContentExtension */;
|
||||
targetProxy = 0632CE2820EC9098003FDF46 /* PBXContainerItemProxy */;
|
||||
};
|
||||
066048112700550600938904 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 066048082700550600938904 /* Intent */;
|
||||
targetProxy = 066048102700550600938904 /* PBXContainerItemProxy */;
|
||||
};
|
||||
06CF784621C7A50300A052D7 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 06CF783F21C7A50300A052D7 /* NotificationServiceExtension */;
|
||||
@ -1058,6 +1060,7 @@
|
||||
children = (
|
||||
063C499620E36BF9001BCA35 /* en */,
|
||||
063C499820E36C15001BCA35 /* zh-Hans */,
|
||||
E3B5CBEB2A94AC26006E25F9 /* tr */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@ -1115,63 +1118,6 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
066048142700550600938904 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 5U8LBRXG3A;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Intent/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Intent;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Fin. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.fin.bark.Intent;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match Development me.fin.bark.Intent";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
066048152700550600938904 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 5U8LBRXG3A;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Intent/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Intent;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Fin. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.fin.bark.Intent;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.fin.bark.Intent";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
0661A54F204FDA4100965E4E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@ -1439,15 +1385,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
066048132700550600938904 /* Build configuration list for PBXNativeTarget "Intent" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
066048142700550600938904 /* Debug */,
|
||||
066048152700550600938904 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
0661A53A204FDA4100965E4E /* Build configuration list for PBXProject "Bark" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
import CloudKit
|
||||
import IceCream
|
||||
import IQKeyboardManagerSwift
|
||||
import Material
|
||||
import RealmSwift
|
||||
import UIKit
|
||||
@ -50,6 +51,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
// 必须在应用一开始就配置,否则应用可能提前在配置之前试用了 Realm() ,则会创建两个独立数据库。
|
||||
setupRealm()
|
||||
|
||||
IQKeyboardManager.shared.enable = true
|
||||
|
||||
self.window = UIWindow(frame: UIScreen.main.bounds)
|
||||
let tabBarController = StateStorageTabBarController()
|
||||
tabBarController.tabBar.tintColor = BKColor.grey.darken4
|
||||
@ -148,7 +151,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: "复制内容", style: .default, handler: { _ in
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("CopyContent"), style: .default, handler: { _ in
|
||||
if let copy = userInfo["copy"] as? String {
|
||||
UIPasteboard.general.string = copy
|
||||
}
|
||||
@ -156,7 +159,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
UIPasteboard.general.string = body
|
||||
}
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: "更多操作", style: .default, handler: { _ in
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("MoreActions"), style: .default, handler: { _ in
|
||||
var shareContent = ""
|
||||
if let title = title {
|
||||
shareContent += "\(title)\n"
|
||||
@ -180,7 +183,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
applicationActivities: nil)
|
||||
controller?.present(activityController, animated: true, completion: nil)
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
|
||||
|
||||
navigationController?.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
@ -207,7 +210,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
ServerManager.shared.syncAllServers()
|
||||
|
||||
|
||||
// 设置 -1 可以清除应用角标,但不清除通知中心的推送
|
||||
// 设置 0 会将通知中心的所有推送一起清空掉
|
||||
UIApplication.shared.applicationIconBadgeNumber = -1
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "216",
|
||||
"green" : "46",
|
||||
"red" : "34"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "233",
|
||||
"green" : "44",
|
||||
"red" : "70"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "214",
|
||||
"green" : "214",
|
||||
"red" : "214"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "92",
|
||||
"green" : "92",
|
||||
"red" : "92"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,11 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
<string>INStartCallIntent</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We are using the camera to scan a QR code</string>
|
||||
<key>GitHub Run Id</key>
|
||||
|
||||
@ -11,17 +11,21 @@ SecureConnection = "Secure Connection (HTTPS)";
|
||||
InsecureConnection = "Insecure Connection (HTTP)";
|
||||
RegisterDevice = "Register Device";
|
||||
|
||||
CustomedNotificationContent = "Customed Notification Content";
|
||||
CustomedNotificationTitle = "Customed Notification Title";
|
||||
CustomedNotificationContent = "Body Text";
|
||||
CustomedNotificationTitle = "Title";
|
||||
|
||||
Notice1 = "Click on the buttons in the upper right corner \nto copy or preview test URL";
|
||||
Notice2 = "Customed notification content and title";
|
||||
Notice1 = "Use the buttons to copy and open sample URLs";
|
||||
Notice2 = "Customize notification title and body text";
|
||||
|
||||
Copy = "Copied!";
|
||||
Copy2 = "Copy";
|
||||
CopyAll = "Copy All";
|
||||
CopyContent = "Copy the Content";
|
||||
Cancel = "Cancel";
|
||||
|
||||
AllowNotifications = "Notifications have been turned off, Please allow notifications and try again";
|
||||
MoreActions = "More Actions";
|
||||
|
||||
AllowNotifications = "Notifications have been turned off. Please allow notifications in the Settings app.";
|
||||
|
||||
UnregisteredDevice = "Unregistered Device";
|
||||
|
||||
@ -29,45 +33,45 @@ ServerError = "Server Error";
|
||||
|
||||
ServerAddress = "Server Address";
|
||||
|
||||
ServerExample = "Enter the server address, for example: https://api.day.app";
|
||||
ServerExample = "Enter the server address, e.g., https://api.day.app";
|
||||
|
||||
DeploymentDocuments = "Deployment Documentation";
|
||||
DeploymentDocuments = "Deploy a self-hosted Bark server";
|
||||
|
||||
AddServer = "Add Customed Server";
|
||||
AddServer = "Add a Server";
|
||||
|
||||
AddedSuccessfully = "Added Successfully!";
|
||||
AddedSuccessfully = "Server has been added successfully.";
|
||||
|
||||
InvalidServer = "Invalid server! please try again.";
|
||||
InvalidServer = "Server address is invalid.";
|
||||
|
||||
InvalidURL = "Invalid URL!";
|
||||
InvalidURL = "URL is invalid.";
|
||||
|
||||
urlParameter = "Clicking on the notification will jump \nto this URL in the parameter";
|
||||
urlParameter = "Open an URL by clicking on a notification, URL schemes are supported.";
|
||||
|
||||
copyParameter = "If the URL has a copy parameter then the value of the copy \nparameter will be copied";
|
||||
copyParameter = "Specify clipboard content by passing through the 'copy' parameter";
|
||||
|
||||
automaticallyCopyTitle = "Automatically copy push content (No longer available after iOS14.5)";
|
||||
automaticallyCopy = "Automatically copy push content \nwhen a push is received\nAfter iOS 14.5, long press or pull down on a push to trigger auto-copy, before iOS 14.5 you don't need to do anything";
|
||||
automaticallyCopyTitle = "Auto Copy Body Text (iOS 14.4 and below)";
|
||||
automaticallyCopy = "Automatically copy the body text of a notification, only for iOS 14.4 and below. For iOS 14.5 and above, long press or pull down the notification to copy.";
|
||||
|
||||
historyMessage = "Message History";
|
||||
|
||||
unknown = "Unknown";
|
||||
available = "Available";
|
||||
restricted = "Restricted";
|
||||
unknown = "Unknown Status";
|
||||
available = "Enabled";
|
||||
restricted = "Disabled";
|
||||
settings = "Settings";
|
||||
|
||||
iCloudSatatus = "iCloud sync";
|
||||
iCloudSync = "Notification messages will be synced when iCloud is available";
|
||||
iCloudSatatus = "iCloud Sync";
|
||||
iCloudSync = "Sync notifications through iCloud";
|
||||
|
||||
defaultArchiveSettings = "Archive by default";
|
||||
archiveNote = "When the isArchive parameter is not specified in the push request URL, whether to archive the notification message will be decided according to this setting";
|
||||
defaultArchiveSettings = "Archive by Default";
|
||||
archiveNote = "Archive notifications by default if the 'isArchive' parameter is unset.";
|
||||
|
||||
archiveNotificationMessageTitle = "Archive notification message";
|
||||
archiveNotificationMessage = "Notification message will be archived when isArchive value is 1, and will not be archived when the value is 0.\nIf the isArchive parameter is not specified, the notification message will be archived according to the default settings";
|
||||
archiveNotificationMessageTitle = "Archive Notification";
|
||||
archiveNotificationMessage = "Archive a notification by passing 'isArchive=1'. Prevent archiving by passing 'isArchive=0'. Default settings will apply if the parameter is unset.";
|
||||
|
||||
notificationSound = "Notification sound";
|
||||
previewSound = "Click to preview";
|
||||
setSounds = "You can set different sounds for push notifications";
|
||||
viewAllSounds = "View all sounds";
|
||||
notificationSound = "Alert Sound";
|
||||
previewSound = "Click to Play";
|
||||
setSounds = "Select an alert sound for a notification.";
|
||||
viewAllSounds = "Click here to view all available sounds.";
|
||||
|
||||
service = "Service";
|
||||
|
||||
@ -79,43 +83,69 @@ allTime = "All time";
|
||||
clearFrom = "Clear from:";
|
||||
clear = "Clear";
|
||||
|
||||
group = "Group";
|
||||
group = "Groups";
|
||||
done = "Done";
|
||||
default = "Default";
|
||||
hideAllGroups = "Unselect All Groups";
|
||||
showAllGroups = "Select All Groups";
|
||||
groupMessagesNotice = "Grouping of messages allows you to view messages by group.";
|
||||
messageGroup = "Message grouping";
|
||||
groupMessagesNotice = "Let iOS sort notifications by groups.";
|
||||
messageGroup = "Notification Grouping";
|
||||
|
||||
info = "Info";
|
||||
buildDesc = "Bark was uploaded to the App Store by Github Actions.";
|
||||
buildDesc = "Bark uses GitHub Actions to build and publish itself to the App Store. You can verify the ‘run_id’ in the URL to ensure Bark was built from its original open-source codebase.";
|
||||
|
||||
other = "Other";
|
||||
faq = "FAQ";
|
||||
faq = "FAQs";
|
||||
appSC = "App Source Code";
|
||||
backendSC = "Backend Source Code";
|
||||
|
||||
notificationIcon = "Custom Icon";
|
||||
notificationIconNotice = "Set a custom icon for the push and the set icon will replace the default Bark icon. Icons are automatically cached locally.";
|
||||
notificationIcon = "Icon";
|
||||
notificationIconNotice = "Display a custom icon on a notification.";
|
||||
|
||||
interruptionLevel = "Time Sensitive notifications";
|
||||
interruptionLevelNotice = "The interruption level can be set for notifications; if not set, the default is active.\n\nAvailable parameter values:\nactive: The system presents the notification immediately, lights up the screen.\ntimeSensitive: May be presented during Do Not Disturb\npassive: Added to the notification list; does not light up screen or play sound.";
|
||||
interruptionLevel = "Time Sensitive Notifications";
|
||||
interruptionLevelNotice = "Set importance and delivery timing of a notification. Available values:\nactive (default): presents the notification immediately, lights up the screen, and can play a sound.\ntimeSensitive: similar to active, but can break through system controls such as Notification Summary and Focus.\npassive: adds the notification to the notification list without lighting up the screen or playing a sound.";
|
||||
|
||||
badge = "Modify a Notification Badge";
|
||||
badgeNotice = "The notification badge can be set with a number.";
|
||||
badge = "Badge Count";
|
||||
badgeNotice = "Specify a number on Bark app’s icon when a notification arrives.";
|
||||
|
||||
deviceTokenInfo = "Token for APNs, click to copy to clipboard.";
|
||||
deviceTokenInfo = "Token for APNs. Click to copy.";
|
||||
|
||||
|
||||
serverList = "Server List";
|
||||
copyAddressAndKey = "Copy address and key";
|
||||
copyAddressAndKey = "Copy Address and Key";
|
||||
resetKey = "Reset or Restore Key";
|
||||
resetKeyDesc = "Click confirm to reset, enter the old Key to restore";
|
||||
resetKeyPlaceholder = "Do not enter or enter the old Key";
|
||||
resetKeyDesc = "Leave blank to reset. Enter an old key to restore.";
|
||||
resetKeyPlaceholder = "";
|
||||
confirm = "Confirm";
|
||||
deleteServer = "Delete server";
|
||||
confirmDeleteServer = "Sure to delete the server?";
|
||||
deleteFailed = "Cannot be deleted, the server must keep at least one";
|
||||
deletedSuccessfully = "Deleted successfully";
|
||||
resetFailed = "Reset failed";
|
||||
resetFailed2 = "Failed to reset, did not get DeviceToken";
|
||||
deleteServer = "Delete Server";
|
||||
confirmDeleteServer = "Are you sure you want to delete this server?";
|
||||
deleteFailed = "You must have at least one server configured.";
|
||||
deletedSuccessfully = "Server has been deleted successfully.";
|
||||
resetFailed = "Reset Failed";
|
||||
resetFailed2 = "Bark could not get a DeviceToken from the server.";
|
||||
export = "Export";
|
||||
import = "Import";
|
||||
exportOrImport = "Export and import messages";
|
||||
items = "messages";
|
||||
|
||||
enterKey = "Please enter %d-bit Key";
|
||||
enterIv = "Please enter 16-bit Iv";
|
||||
encryptionSettings = "Encryption Settings";
|
||||
algorithm = "Algorithm";
|
||||
mode = "Mode";
|
||||
copyExample = "Copy Send Script Example";
|
||||
preview = "Preview";
|
||||
|
||||
pushNotificationEncryption = "Push Notification Encryption";
|
||||
encryptionNotice = "Uses a custom key to encrypt and decrypt the push content when sending and receiving. In this way, the push content will not be obtained or leaked by Bark server and Apple APNs server during transmission.";
|
||||
|
||||
faqUrl = "https://bark.day.app/#/en-us/faq";
|
||||
docUrl = "https://bark.day.app/#/en-us/?id=bark";
|
||||
encryptionUrl = "https://bark.day.app/#/en-us/encryption";
|
||||
deployUrl = "https://bark.day.app/#/en-us/deploy";
|
||||
documentation = "Documentation";
|
||||
ivComment = "IV can be randomly generated, but if it is random, it needs to be passed in the iv parameter.";
|
||||
opensslEncodingComment = "openssl requires Hex encoding of manual keys and IVs, not ASCII encoding.";
|
||||
ciphertextComment = "URL encoding the ciphertext, there may be special characters.";
|
||||
consoleComment = "The console will print";
|
||||
keyComment = "Must be %d bit long";
|
||||
|
||||
151
Bark/tr.lproj/Localizable.strings
Normal file
151
Bark/tr.lproj/Localizable.strings
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
Bark
|
||||
|
||||
Created by furkanipek on 2023/8/22.
|
||||
Copyright © 2018 Fin. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
SecureConnection = "Güvenli Bağlantı (HTTPS)";
|
||||
InsecureConnection = "Güvensiz Bağlantı (HTTP)";
|
||||
RegisterDevice = "Cihazı Kaydet";
|
||||
|
||||
CustomedNotificationContent = "Gövde Metni";
|
||||
CustomedNotificationTitle = "Başlık";
|
||||
|
||||
Notice1 = "Örnek URL'leri kopyalamak ve açmak için düğmeleri kullanın";
|
||||
Notice2 = "Bildirim başlığını ve gövde metnini özelleştirme";
|
||||
|
||||
Copy = "Kopyalandı!";
|
||||
Copy2 = "Kopyala";
|
||||
CopyAll = "Tümünü Kopyala";
|
||||
CopyContent = "İçeriği Kopyala";
|
||||
Cancel = "Vazgeç";
|
||||
|
||||
MoreActions = "Daha Fazla İşlem";
|
||||
|
||||
AllowNotifications = "Bildirimler kapatıldı. Lütfen Ayarlar uygulamasından bildirimlere izin verin.";
|
||||
|
||||
UnregisteredDevice = "Kayıtsız Cihaz";
|
||||
|
||||
ServerError = "Sunucu Hatası";
|
||||
|
||||
ServerAddress = "Sunucu Adresi";
|
||||
|
||||
ServerExample = "Sunucu adresini girin, örneğin, https://api.day.app";
|
||||
|
||||
DeploymentDocuments = "Sunucu tarafı dağıtım dökümanını görüntüleyin";
|
||||
|
||||
AddServer = "Sunucu Ekleme";
|
||||
|
||||
AddedSuccessfully = "Sunucu başarıyla eklendi.";
|
||||
|
||||
InvalidServer = "Sunucu adresi geçersiz.";
|
||||
|
||||
InvalidURL = "URL geçersiz.";
|
||||
|
||||
urlParameter = "Bir bildirime tıklayarak bir URL açın, URL şemaları desteklenir.";
|
||||
|
||||
copyParameter = "'copy' parametresini geçirerek pano içeriğini belirtin";
|
||||
|
||||
automaticallyCopyTitle = "Gövde Metnini Otomatik Kopyala (iOS 14.4 ve altı)";
|
||||
automaticallyCopy = "Bir bildirimin gövde metnini otomatik olarak kopyalayın, yalnızca iOS 14.4 ve altı için. iOS 14.5 ve üzeri sürümlerde, kopyalamak için bildirime uzun basın veya aşağı çekin.";
|
||||
|
||||
historyMessage = "Mesaj Geçmişi";
|
||||
|
||||
unknown = "Bilinmeyen Durum";
|
||||
available = "Etkin";
|
||||
restricted = "Devre Dışı";
|
||||
settings = "Ayarlar";
|
||||
|
||||
iCloudSatatus = "iCloud Senkronizasyonu";
|
||||
iCloudSync = "Bildirimleri iCloud aracılığıyla senkronize edin";
|
||||
|
||||
defaultArchiveSettings = "Varsayılan Olarak Arşivle";
|
||||
archiveNote = "'isArchive' parametresi ayarlanmamışsa varsayılan olarak bildirimleri arşivleyin.";
|
||||
|
||||
archiveNotificationMessageTitle = "Arşiv Bildirimi";
|
||||
archiveNotificationMessage = "'isArchive=1' değerini geçerek bir bildirimi arşivleyin. 'isArchive=0' geçerek arşivlemeyi önleyin. Parametre ayarlanmamışsa varsayılan ayarlar geçerli olacaktır.";
|
||||
|
||||
notificationSound = "Uyarı Sesi";
|
||||
previewSound = "Oynamak için Tıklayın";
|
||||
setSounds = "Bildirim için bir uyarı sesi seçin.";
|
||||
viewAllSounds = "Mevcut tüm sesleri görüntülemek için buraya tıklayın.";
|
||||
|
||||
service = "Hizmet";
|
||||
|
||||
lastHour = "Son bir saat";
|
||||
today = "Bugün";
|
||||
todayAndYesterday = "Dün ve Bugün";
|
||||
allTime = "Tüm zamanlar";
|
||||
|
||||
clearFrom = "Şuradan Temizle:";
|
||||
clear = "Temizle";
|
||||
|
||||
group = "Gruplar";
|
||||
done = "Bitti";
|
||||
default = "Varsayılan";
|
||||
hideAllGroups = "Tüm Grupların Seçimini Kaldır";
|
||||
showAllGroups = "Tüm Grupları Seç";
|
||||
groupMessagesNotice = "iOS'un bildirimleri gruplara göre sıralamasına izin verin.";
|
||||
messageGroup = "Bildirim Gruplandırma";
|
||||
|
||||
info = "Bilgi";
|
||||
buildDesc = "Bark, kendisini oluşturmak ve App Store'da yayınlamak için GitHub Actions'ı kullanır. Bark'ın orijinal açık kaynak kod tabanından oluşturulduğundan emin olmak için URL'deki 'run_id'yi doğrulayabilirsiniz.";
|
||||
|
||||
other = "Diğer";
|
||||
faq = "SSS";
|
||||
appSC = "Uygulama Kaynak Kodu";
|
||||
backendSC = "Arka Uç Kaynak Kodu";
|
||||
|
||||
notificationIcon = "İkon";
|
||||
notificationIconNotice = "Bildirimde özel bir ikon görüntüleyin.";
|
||||
|
||||
interruptionLevel = "Zamana Duyarlı Bildirimler";
|
||||
interruptionLevelNotice = "Bir bildirimin önemini ve teslim zamanlamasını ayarlayın. Kullanılabilir değerler:\nactive (varsayılan): bildirimi hemen sunar, ekranı aydınlatır ve bir ses çalabilir.\ntimeSensitive: aktif'e benzer, ancak Bildirim Özeti ve Odak gibi sistem denetimlerini aşabilir.\npassive: bildirimi ekranı aydınlatmadan veya bir ses çalmadan bildirim listesine ekler.";
|
||||
|
||||
badge = "Rozet Sayısı";
|
||||
badgeNotice = "Bir bildirim geldiğinde Bark uygulamasının simgesinde bir sayı belirtin.";
|
||||
|
||||
deviceTokenInfo = "APNs için token. Kopyalamak için tıklayın.";
|
||||
|
||||
|
||||
serverList = "Sunucu Listesi";
|
||||
copyAddressAndKey = "Adres ve Anahtarı Kopyala";
|
||||
resetKey = "Anahtarı Sıfırla veya Geri Yükle";
|
||||
resetKeyDesc = "Sıfırlamak için boş bırakın. Geri yüklemek için eski bir anahtar girin.";
|
||||
resetKeyPlaceholder = "";
|
||||
confirm = "Onayla";
|
||||
deleteServer = "Sunucuyu Sil";
|
||||
confirmDeleteServer = "Bu sunucuyu silmek istediğinizden emin misiniz?";
|
||||
deleteFailed = "Yapılandırılmış en az bir sunucunuz olmalıdır.";
|
||||
deletedSuccessfully = "Sunucu başarıyla silindi.";
|
||||
resetFailed = "Sıfırlama Başarısız Oldu";
|
||||
resetFailed2 = "Bark sunucudan bir DeviceToken alamadı.";
|
||||
export = "Dışa Aktar";
|
||||
import = "İçe Aktar";
|
||||
exportOrImport = "Mesajları dışa ve içe aktarma";
|
||||
items = "mesajlar";
|
||||
|
||||
enterKey = "Lütfen %d-bit Anahtar girin";
|
||||
enterIv = "Lütfen 16 bit Iv girin";
|
||||
encryptionSettings = "Şifreleme Ayarları";
|
||||
algorithm = "Algoritma";
|
||||
mode = "Mod";
|
||||
copyExample = "Kopya Gönderme Komut Dosyası Örneği";
|
||||
preview = "Önizleme";
|
||||
|
||||
pushNotificationEncryption = "Anlık Bildirim Şifreleme";
|
||||
encryptionNotice = "Gönderirken ve alırken push içeriğini şifrelemek ve şifresini çözmek için özel bir anahtar kullanır. Bu sayede push içeriği iletim sırasında Bark sunucusu ve Apple APNs sunucusu tarafından elde edilemez veya sızdırılamaz.";
|
||||
|
||||
faqUrl = "https://bark.day.app/#/tr/faq";
|
||||
docUrl = "https://bark.day.app/#/tr/?id=bark";
|
||||
encryptionUrl = "https://bark.day.app/#/tr/encryption";
|
||||
deployUrl = "https://bark.day.app/#/tr/deploy";
|
||||
documentation = "Dokümantasyon";
|
||||
ivComment = "IV rastgele oluşturulabilir, ancak rastgele ise iv parametresinde belirtilmesi gerekir.";
|
||||
opensslEncodingComment = "openssl, manuel anahtarların ve IV'lerin ASCII kodlamasını değil, Hex kodlamasını gerektirir.";
|
||||
ciphertextComment = "URL şifreli metni kodlarken özel karakterler olabilir.";
|
||||
consoleComment = "Konsola şunları yazdırır";
|
||||
keyComment = "%d bit uzunluğunda olmalıdır";
|
||||
@ -19,8 +19,12 @@ Notice2 = "推送标题的字号比推送内容粗一点";
|
||||
|
||||
Copy = "复制成功";
|
||||
Copy2 = "复制";
|
||||
CopyAll = "复制全部内容";
|
||||
CopyContent = "复制内容";
|
||||
Cancel = "取消";
|
||||
|
||||
MoreActions = "更多操作";
|
||||
|
||||
AllowNotifications = "绑定设备需要推送。请打开推送后重试";
|
||||
|
||||
UnregisteredDevice = "设备未注册,不能使用推送服务";
|
||||
@ -41,7 +45,7 @@ InvalidServer = "填写的服务器无效,请重试!";
|
||||
|
||||
InvalidURL = "输入的URL好像不对劲!";
|
||||
|
||||
urlParameter = "携带url参数时,点击推送会跳转到这个URL";
|
||||
urlParameter = "携带url参数时,点击推送会跳转到这个URL,支持跳转URL Scheme";
|
||||
|
||||
copyParameter = "下拉推送、锁屏界面左滑查看推送时,可以选择复制推送内容\n携带copy参数时,将只复制copy参数的值";
|
||||
|
||||
@ -62,7 +66,7 @@ defaultArchiveSettings = "默认保存";
|
||||
archiveNote = "当推送请求URL没有指定 isArchive 参数时,将按照此设置来决定是否保存通知消息";
|
||||
|
||||
archiveNotificationMessageTitle = "自动保存通知消息";
|
||||
archiveNotificationMessage = "当设置 isArchive 值为 1 时,则会自动保存这条推送消息,设置为其他值时,则不会保存。\n如果不指定 isArchive 参数时,则按照默认设置保存消息,可以在 消息->设置->默认保存消息 更改默认保存设置";
|
||||
archiveNotificationMessage = "当设置 isArchive 值为 1 时,则会自动保存这条推送消息,设置为其他值时,则不会保存。\n如果不指定 isArchive 参数时,则按照默认设置保存消息,可以在 设置->默认保存消息 更改默认保存设置";
|
||||
|
||||
notificationSound = "推送铃声";
|
||||
previewSound = "点击可预览";
|
||||
@ -122,3 +126,29 @@ deleteFailed = "不能删除,服务器至少需保留一个";
|
||||
deletedSuccessfully = "删除成功";
|
||||
resetFailed = "重置失败";
|
||||
resetFailed2 = "重置失败,没有获取到 DeviceToken";
|
||||
export = "导出";
|
||||
import = "导入";
|
||||
exportOrImport = "导出或导入消息列表";
|
||||
items = "条消息";
|
||||
|
||||
enterKey = "请输入%d位Key";
|
||||
enterIv = "请输入16位Iv";
|
||||
encryptionSettings = "加密设置";
|
||||
algorithm = "算法";
|
||||
mode = "模式";
|
||||
copyExample = "复制发送脚本示例";
|
||||
preview = "预览";
|
||||
|
||||
pushNotificationEncryption = "推送加密";
|
||||
encryptionNotice = "在发送和接收推送时,对推送内容进行加密和解密。这样,推送内容在传输过程中就不会被 Bark 服务器和苹果 APNs 服务器获取或泄露,从而保护你的隐私";
|
||||
|
||||
faqUrl = "https://bark.day.app/#/faq";
|
||||
docUrl = "https://bark.day.app/#/?id=bark";
|
||||
encryptionUrl = "https://bark.day.app/#/encryption";
|
||||
deployUrl = "https://bark.day.app/#/deploy";
|
||||
documentation = "使用文档";
|
||||
ivComment = "IV可以是随机生成的,但如果是随机的就需要放在 iv 参数里传递。";
|
||||
opensslEncodingComment = "OpenSSL 要求输入的 Key 和 IV 需使用十六进制编码。";
|
||||
ciphertextComment = "密文可能有特殊字符,所以记得 URL 编码一下。";
|
||||
consoleComment = "控制台将打印";
|
||||
keyComment = "必须%d位";
|
||||
|
||||
23
Common/ArchiveSettingRelay.swift
Normal file
23
Common/ArchiveSettingRelay.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// ArchiveSettingRelay.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/1/30.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import RxCocoa
|
||||
import UIKit
|
||||
class ArchiveSettingRelay: NSObject {
|
||||
static let shared = ArchiveSettingRelay()
|
||||
let isArchiveRelay: BehaviorRelay<Bool>
|
||||
|
||||
override private init() {
|
||||
self.isArchiveRelay = BehaviorRelay<Bool>(value: ArchiveSettingManager.shared.isArchive)
|
||||
super.init()
|
||||
|
||||
self.isArchiveRelay.subscribe { val in
|
||||
ArchiveSettingManager.shared.isArchive = val
|
||||
}.disposed(by: rx.disposeBag)
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ class BKColor: NSObject {
|
||||
public static let darken3 = UIColor(named: "grey_darken3")!
|
||||
public static let darken4 = UIColor(named: "grey_darken4")!
|
||||
public static let lighten1 = UIColor(named: "grey_lighten1")!
|
||||
public static let lighten2 = UIColor(named: "grey_lighten2")!
|
||||
public static let lighten3 = UIColor(named: "grey_lighten3")!
|
||||
public static let lighten4 = UIColor(named: "grey_lighten4")!
|
||||
public static let lighten5 = UIColor(named: "grey_lighten5")!
|
||||
@ -25,6 +26,7 @@ class BKColor: NSObject {
|
||||
enum blue {
|
||||
public static let base = UIColor(named: "blue_base")!
|
||||
public static let darken1 = UIColor(named: "blue_darken1")!
|
||||
public static let darken5 = UIColor(named: "blue_darken5")!
|
||||
}
|
||||
|
||||
enum lightBlue {
|
||||
|
||||
40
Common/CryptoSettingManager.swift
Normal file
40
Common/CryptoSettingManager.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// CryptoSettingManager.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/3/2.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CryptoSettingManager: NSObject {
|
||||
static let shared = CryptoSettingManager()
|
||||
let defaults = UserDefaults(suiteName: "group.bark")
|
||||
var fields: CryptoSettingFields? {
|
||||
get {
|
||||
guard let data:Data = defaults?.value(forKey: "cryptoSettingFields") as? Data else {
|
||||
return nil
|
||||
}
|
||||
guard let fields = try? JSONDecoder().decode(CryptoSettingFields.self, from: data) else {
|
||||
return nil
|
||||
}
|
||||
return fields
|
||||
}
|
||||
set {
|
||||
guard let newValue = newValue else {
|
||||
defaults?.removeObject(forKey: "cryptoSettingFields")
|
||||
return
|
||||
}
|
||||
guard let encoded = try? JSONEncoder().encode(newValue) else{
|
||||
return
|
||||
}
|
||||
defaults?.set(encoded, forKey: "cryptoSettingFields")
|
||||
}
|
||||
}
|
||||
|
||||
override private init() {
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
24
Common/CryptoSettingRelay.swift
Normal file
24
Common/CryptoSettingRelay.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// CryptoSettingRelay.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/3/7.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RxCocoa
|
||||
|
||||
class CryptoSettingRelay: NSObject {
|
||||
static let shared = CryptoSettingRelay()
|
||||
let fields: BehaviorRelay<CryptoSettingFields?>
|
||||
|
||||
override private init() {
|
||||
self.fields = BehaviorRelay<CryptoSettingFields?>(value: CryptoSettingManager.shared.fields)
|
||||
super.init()
|
||||
|
||||
self.fields.subscribe { val in
|
||||
CryptoSettingManager.shared.fields = val
|
||||
}.disposed(by: rx.disposeBag)
|
||||
}
|
||||
}
|
||||
@ -28,10 +28,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func NSLocalizedString(_ key: String) -> String {
|
||||
return NSLocalizedString(key, comment: "")
|
||||
}
|
||||
|
||||
let kNavigationHeight: CGFloat = {
|
||||
kSafeAreaInsets.top + 44
|
||||
}()
|
||||
|
||||
33
Common/Error+Extension.swift
Normal file
33
Common/Error+Extension.swift
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// Error+Extension.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/3/3.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String: Error {}
|
||||
|
||||
public enum ApiError: Swift.Error {
|
||||
case Error(info: String)
|
||||
case AccountBanned(info: String)
|
||||
}
|
||||
|
||||
extension Swift.Error {
|
||||
func rawString() -> String {
|
||||
if let err = self as? String {
|
||||
return err
|
||||
}
|
||||
guard let err = self as? ApiError else {
|
||||
return self.localizedDescription
|
||||
}
|
||||
switch err {
|
||||
case .Error(let info):
|
||||
return info
|
||||
case .AccountBanned(let info):
|
||||
return info
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,25 +14,6 @@ import RxSwift
|
||||
import SwiftyJSON
|
||||
import UIKit
|
||||
|
||||
public enum ApiError: Swift.Error {
|
||||
case Error(info: String)
|
||||
case AccountBanned(info: String)
|
||||
}
|
||||
|
||||
extension Swift.Error {
|
||||
func rawString() -> String {
|
||||
guard let err = self as? ApiError else {
|
||||
return self.localizedDescription
|
||||
}
|
||||
switch err {
|
||||
case .Error(let info):
|
||||
return info
|
||||
case .AccountBanned(let info):
|
||||
return info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Observable where Element: Moya.Response {
|
||||
/// 过滤 HTTP 错误,例如超时,请求失败等
|
||||
func filterHttpError() -> Observable<Result<Element, ApiError>> {
|
||||
|
||||
13
Common/NSLocalizedString+Extension.swift
Normal file
13
Common/NSLocalizedString+Extension.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// NSLocalizedString+Extension.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/3/3.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func NSLocalizedString(_ key: String) -> String {
|
||||
return NSLocalizedString(key, comment: "")
|
||||
}
|
||||
@ -12,7 +12,7 @@ import UIKit
|
||||
|
||||
private var prepareForReuseBag: Int8 = 0
|
||||
|
||||
@objc public protocol Reusable: class {
|
||||
@objc public protocol Reusable: AnyObject {
|
||||
func prepareForReuse()
|
||||
}
|
||||
|
||||
@ -21,10 +21,6 @@ extension UITableViewHeaderFooterView: Reusable {}
|
||||
extension UICollectionReusableView: Reusable {}
|
||||
|
||||
extension Reactive where Base: Reusable {
|
||||
var prepareForReuse: Observable<Void> {
|
||||
return Observable.of(sentMessage(#selector(Base.prepareForReuse)).map { _ in }, deallocated).merge()
|
||||
}
|
||||
|
||||
var reuseBag: DisposeBag {
|
||||
MainScheduler.ensureExecutingOnScheduler()
|
||||
|
||||
@ -36,6 +32,7 @@ extension Reactive where Base: Reusable {
|
||||
objc_setAssociatedObject(base, &prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
|
||||
|
||||
_ = sentMessage(#selector(Base.prepareForReuse))
|
||||
.take(until: deallocated)
|
||||
.subscribe(onNext: { [weak base] _ in
|
||||
guard let strongBase = base else {
|
||||
return
|
||||
|
||||
242
Controller/CryptoSettingController.swift
Normal file
242
Controller/CryptoSettingController.swift
Normal file
@ -0,0 +1,242 @@
|
||||
//
|
||||
// CryptoSettingController.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/11/10.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
class CryptoSettingController: BaseViewController<CryptoSettingViewModel> {
|
||||
let algorithmFeild = DropBoxView(values: ["AES128", "AES192", "AES256"])
|
||||
let modeFeild = DropBoxView(values: ["CBC", "ECB"])
|
||||
let paddingField = DropBoxView(values: ["pkcs7"])
|
||||
|
||||
let keyTextField: BorderTextField = {
|
||||
let textField = BorderTextField(title: "Key")
|
||||
textField.font = UIFont.systemFont(ofSize: 14)
|
||||
textField.placeholder = String(format: NSLocalizedString("enterKey"), 16)
|
||||
return textField
|
||||
}()
|
||||
|
||||
let ivTextField: BorderTextField = {
|
||||
let textField = BorderTextField(title: "IV")
|
||||
textField.font = UIFont.systemFont(ofSize: 14)
|
||||
textField.placeholder = NSLocalizedString("enterIv")
|
||||
return textField
|
||||
}()
|
||||
|
||||
let doneButton: BKButton = {
|
||||
let btn = BKButton()
|
||||
btn.setTitle(NSLocalizedString("done"), for: .normal)
|
||||
btn.setTitleColor(BKColor.lightBlue.darken3, for: .normal)
|
||||
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
|
||||
btn.fontSize = 14
|
||||
return btn
|
||||
}()
|
||||
|
||||
let copyButton: UIButton = {
|
||||
let btn = GradientButton()
|
||||
btn.setTitle(NSLocalizedString("copyExample"), for: .normal)
|
||||
btn.setTitleColor(UIColor.white, for: .normal)
|
||||
btn.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .medium)
|
||||
btn.layer.cornerRadius = 8
|
||||
btn.clipsToBounds = true
|
||||
btn.applyGradient(
|
||||
withColours: [
|
||||
UIColor(r255: 36, g255: 51, b255: 236),
|
||||
UIColor(r255: 70, g255: 44, b255: 233),
|
||||
],
|
||||
gradientOrientation: .horizontal
|
||||
)
|
||||
return btn
|
||||
}()
|
||||
|
||||
let scrollView = UIScrollView()
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override func makeUI() {
|
||||
self.title = NSLocalizedString("encryptionSettings")
|
||||
self.navigationItem.setRightBarButtonItem(item: UIBarButtonItem(customView: doneButton))
|
||||
|
||||
self.view.addSubview(scrollView)
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
func getTitleLabel(title: String) -> UILabel {
|
||||
let label = UILabel()
|
||||
label.font = UIFont.systemFont(ofSize: 14)
|
||||
label.textColor = BKColor.grey.darken4
|
||||
label.text = title
|
||||
return label
|
||||
}
|
||||
|
||||
let algorithmLabel = getTitleLabel(title: NSLocalizedString("algorithm"))
|
||||
let modeLabel = getTitleLabel(title: NSLocalizedString("mode"))
|
||||
let paddingLabel = getTitleLabel(title: "Padding")
|
||||
let keyLabel = getTitleLabel(title: "Key")
|
||||
let ivLabel = getTitleLabel(title: "Iv")
|
||||
|
||||
self.scrollView.addSubview(algorithmLabel)
|
||||
self.scrollView.addSubview(algorithmFeild)
|
||||
|
||||
self.scrollView.addSubview(modeLabel)
|
||||
self.scrollView.addSubview(modeFeild)
|
||||
|
||||
self.scrollView.addSubview(paddingLabel)
|
||||
self.scrollView.addSubview(paddingField)
|
||||
|
||||
self.scrollView.addSubview(keyLabel)
|
||||
self.scrollView.addSubview(keyTextField)
|
||||
|
||||
self.scrollView.addSubview(ivLabel)
|
||||
self.scrollView.addSubview(ivTextField)
|
||||
|
||||
self.scrollView.addSubview(copyButton)
|
||||
|
||||
self.view.backgroundColor = UIColor.white
|
||||
|
||||
algorithmLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(24)
|
||||
make.left.equalTo(24)
|
||||
}
|
||||
algorithmFeild.snp.makeConstraints { make in
|
||||
make.top.equalTo(algorithmLabel.snp.bottom).offset(5)
|
||||
make.left.equalTo(20)
|
||||
make.right.equalTo(-20)
|
||||
make.height.equalTo(45)
|
||||
make.width.equalToSuperview().offset(-40)
|
||||
}
|
||||
|
||||
modeLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(algorithmFeild.snp.bottom).offset(20)
|
||||
make.left.equalTo(algorithmLabel)
|
||||
}
|
||||
modeFeild.snp.makeConstraints { make in
|
||||
make.left.right.height.equalTo(algorithmFeild)
|
||||
make.top.equalTo(modeLabel.snp.bottom).offset(5)
|
||||
}
|
||||
|
||||
paddingLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(modeFeild.snp.bottom).offset(20)
|
||||
make.left.equalTo(algorithmLabel)
|
||||
}
|
||||
paddingField.snp.makeConstraints { make in
|
||||
make.left.right.height.equalTo(modeFeild)
|
||||
make.top.equalTo(paddingLabel.snp.bottom).offset(5)
|
||||
}
|
||||
|
||||
keyLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(paddingField.snp.bottom).offset(20)
|
||||
make.left.equalTo(algorithmLabel)
|
||||
}
|
||||
keyTextField.snp.makeConstraints { make in
|
||||
make.left.right.height.equalTo(paddingField)
|
||||
make.top.equalTo(keyLabel.snp.bottom).offset(5)
|
||||
}
|
||||
|
||||
ivLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(keyTextField.snp.bottom).offset(20)
|
||||
make.left.equalTo(algorithmLabel)
|
||||
}
|
||||
ivTextField.snp.makeConstraints { make in
|
||||
make.left.right.height.equalTo(keyTextField)
|
||||
make.top.equalTo(ivLabel.snp.bottom).offset(5)
|
||||
}
|
||||
|
||||
copyButton.snp.makeConstraints { make in
|
||||
make.left.equalTo(ivTextField)
|
||||
make.right.equalTo(ivTextField)
|
||||
make.height.equalTo(42)
|
||||
make.top.equalTo(ivTextField.snp.bottom).offset(25)
|
||||
make.bottom.equalToSuperview().offset(-20)
|
||||
}
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(resign)))
|
||||
}
|
||||
|
||||
@objc func resign() {
|
||||
self.view.endEditing(true)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.view.backgroundColor = BKColor.white
|
||||
}
|
||||
|
||||
override func bindViewModel() {
|
||||
|
||||
func getFieldValues() -> CryptoSettingFields {
|
||||
return CryptoSettingFields(
|
||||
algorithm: self.algorithmFeild.currentValue!,
|
||||
mode: self.modeFeild.currentValue!,
|
||||
padding: self.paddingField.currentValue!,
|
||||
key: self.keyTextField.text,
|
||||
iv: self.ivTextField.text
|
||||
)
|
||||
}
|
||||
|
||||
let output = viewModel.transform(input: CryptoSettingViewModel.Input(
|
||||
algorithmChanged: self.algorithmFeild
|
||||
.rx
|
||||
.currentValueChanged
|
||||
.compactMap { $0 }
|
||||
.asDriver(onErrorDriveWith: .empty()),
|
||||
|
||||
copyScript: copyButton
|
||||
.rx
|
||||
.tap
|
||||
.map { getFieldValues() }
|
||||
.asDriver(onErrorDriveWith: .empty()),
|
||||
|
||||
done: doneButton
|
||||
.rx
|
||||
.tap
|
||||
.map { getFieldValues() }
|
||||
.asDriver(onErrorDriveWith: .empty())
|
||||
))
|
||||
|
||||
output.initial.drive(onNext: { [weak self] val in
|
||||
self?.algorithmFeild.values = val.algorithmList.map { $0.rawValue }
|
||||
self?.modeFeild.values = val.modeList
|
||||
self?.paddingField.values = val.paddingList
|
||||
if let fields = val.initialFields {
|
||||
self?.algorithmFeild.currentValue = fields.algorithm
|
||||
self?.modeFeild.currentValue = fields.mode
|
||||
self?.paddingField.currentValue = fields.padding
|
||||
self?.keyTextField.text = fields.key
|
||||
self?.ivTextField.text = fields.iv
|
||||
}
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
output.modeListChanged
|
||||
.drive(self.modeFeild.rx.values)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
output.paddingListChanged
|
||||
.drive(self.paddingField.rx.values)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
output.keyLenghtChanged.drive(onNext: { [weak self] keyLenght in
|
||||
self?.keyTextField.placeholder = String(format: NSLocalizedString("enterKey"), keyLenght)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
output.showSnackbar.drive(onNext: { text in
|
||||
HUDError(text)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
output.done.drive(onNext: { [weak self] in
|
||||
self?.navigationController?.dismiss(animated: true)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
output.copy.drive(onNext: { text in
|
||||
UIPasteboard.general.string = text
|
||||
HUDSuccess(NSLocalizedString("Copy"))
|
||||
}).disposed(by: rx.disposeBag)
|
||||
}
|
||||
}
|
||||
150
Controller/CryptoSettingViewModel.swift
Normal file
150
Controller/CryptoSettingViewModel.swift
Normal file
@ -0,0 +1,150 @@
|
||||
//
|
||||
// CryptoSettingViewModel.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/11/10.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import CryptoSwift
|
||||
import Foundation
|
||||
import RxCocoa
|
||||
import RxSwift
|
||||
|
||||
class CryptoSettingViewModel: ViewModel, ViewModelType {
|
||||
struct Input {
|
||||
let algorithmChanged: Driver<String>
|
||||
let copyScript: Driver<CryptoSettingFields>
|
||||
let done: Driver<CryptoSettingFields>
|
||||
}
|
||||
|
||||
struct Output {
|
||||
let initial: Driver<(algorithmList: [Algorithm], modeList: [String], paddingList: [String], initialFields: CryptoSettingFields?)>
|
||||
let modeListChanged: Driver<[String]>
|
||||
let paddingListChanged: Driver<[String]>
|
||||
let keyLenghtChanged: Driver<Int>
|
||||
let showSnackbar: Driver<String>
|
||||
let done: Driver<Void>
|
||||
let copy: Driver<String>
|
||||
}
|
||||
|
||||
struct Dependencies {
|
||||
let settingFieldRelay: BehaviorRelay<CryptoSettingFields?>
|
||||
let deviceKey: Driver<String>
|
||||
let serverAddress: Driver<String>
|
||||
}
|
||||
|
||||
private let dependencies: Dependencies
|
||||
|
||||
init(dependencies: Dependencies =
|
||||
Dependencies(
|
||||
settingFieldRelay: CryptoSettingRelay.shared.fields,
|
||||
// Key 好像没有对应的事件流,先“just”,懒得写了
|
||||
deviceKey: Driver.just(ServerManager.shared.currentServer.key),
|
||||
serverAddress: Driver.just(ServerManager.shared.currentServer.address)
|
||||
)
|
||||
) {
|
||||
self.dependencies = dependencies
|
||||
}
|
||||
|
||||
func transform(input: Input) -> Output {
|
||||
|
||||
let showSnackbar = PublishRelay<String>()
|
||||
|
||||
let modeList = input
|
||||
.algorithmChanged
|
||||
.compactMap { Algorithm(rawValue: $0) }
|
||||
.map { $0.modes }
|
||||
|
||||
let keyLenght =
|
||||
Driver.merge([
|
||||
Driver.just(dependencies.settingFieldRelay.value)
|
||||
.compactMap { $0 }
|
||||
.compactMap { Algorithm(rawValue: $0.algorithm)?.keyLenght },
|
||||
input
|
||||
.algorithmChanged
|
||||
.compactMap { Algorithm(rawValue: $0)?.keyLenght },
|
||||
])
|
||||
|
||||
// 保存配置
|
||||
let done = input.done
|
||||
.filter { fields in
|
||||
do {
|
||||
_ = try AESCryptoModel(cryptoFields: fields)
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
showSnackbar.accept(error.rawString())
|
||||
return false
|
||||
}
|
||||
}
|
||||
done.drive(onNext: { [weak self] fields in
|
||||
// 保存设置
|
||||
self?.dependencies.settingFieldRelay.accept(fields)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
let copyScript = input.copyScript
|
||||
.filter { [weak self] fields in
|
||||
do {
|
||||
_ = try AESCryptoModel(cryptoFields: fields)
|
||||
// 保存配置
|
||||
self?.dependencies.settingFieldRelay.accept(fields)
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
showSnackbar.accept(error.rawString())
|
||||
return false
|
||||
}
|
||||
}
|
||||
let copy = Driver.combineLatest(copyScript, dependencies.deviceKey, dependencies.serverAddress)
|
||||
.map { fields, deviceKey,serverAddress in
|
||||
let key = fields.key ?? ""
|
||||
let iv = fields.iv ?? ""
|
||||
return
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Documentation: \(NSLocalizedString("encryptionUrl"))
|
||||
|
||||
set -e
|
||||
|
||||
# bark key
|
||||
deviceKey='\(deviceKey)'
|
||||
# push payload
|
||||
json='{"body": "test", "sound": "birdsong"}'
|
||||
|
||||
# \(String(format: NSLocalizedString("keyComment"), Int(fields.algorithm.suffix(3))! / 8))
|
||||
key='\(key)'
|
||||
# \(NSLocalizedString("ivComment"))
|
||||
iv='\(iv)'
|
||||
|
||||
# \(NSLocalizedString("opensslEncodingComment"))
|
||||
key=$(printf $key | xxd -ps -c 200)
|
||||
iv=$(printf $iv | xxd -ps -c 200)
|
||||
|
||||
ciphertext=$(echo -n $json | openssl enc -aes-\(fields.algorithm.suffix(3))-\(fields.mode.lowercased()) -K $key \(iv.count > 0 ? "-iv $iv " : "")| base64)
|
||||
|
||||
# \(NSLocalizedString("consoleComment")) "\((try? AESCryptoModel(cryptoFields: fields).encrypt(text: "{\"body\": \"test\", \"sound\": \"birdsong\"}")) ?? "")"
|
||||
echo $ciphertext
|
||||
|
||||
# \(NSLocalizedString("ciphertextComment"))
|
||||
curl --data-urlencode "ciphertext=$ciphertext"\( iv.count == 0 ? "" : " --data-urlencode \"iv=\(iv)\"") \(serverAddress)/$deviceKey
|
||||
"""
|
||||
}
|
||||
|
||||
return Output(
|
||||
initial: Driver.just((
|
||||
algorithmList: [Algorithm.aes128, Algorithm.aes192, Algorithm.aes256],
|
||||
modeList: ["CBC", "ECB"],
|
||||
paddingList: ["okcs7"],
|
||||
initialFields: dependencies.settingFieldRelay.value
|
||||
)),
|
||||
modeListChanged: modeList,
|
||||
paddingListChanged: Driver.just(["pkcs7"]),
|
||||
keyLenghtChanged: keyLenght,
|
||||
showSnackbar: showSnackbar.asDriver(onErrorDriveWith: .empty()),
|
||||
done: done.map { _ in () },
|
||||
copy: copy
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,7 @@ class GroupFilterViewController: BaseViewController<GroupFilterViewModel> {
|
||||
let doneButton: BKButton = {
|
||||
let btn = BKButton()
|
||||
btn.setTitle(NSLocalizedString("done"), for: .normal)
|
||||
btn.setTitleColor(Color.lightBlue.darken3, for: .normal)
|
||||
btn.setTitleColor(BKColor.lightBlue.darken3, for: .normal)
|
||||
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
|
||||
btn.fontSize = 14
|
||||
return btn
|
||||
|
||||
@ -202,7 +202,10 @@ class HomeViewController: BaseViewController<HomeViewModel> {
|
||||
else if let viewModel = viewModel as? SoundsViewModel {
|
||||
viewController = SoundsViewController(viewModel: viewModel)
|
||||
}
|
||||
|
||||
else if let viewModel = viewModel as? CryptoSettingViewModel {
|
||||
self.navigationController?.present(BarkNavigationController(rootViewController: CryptoSettingController(viewModel: viewModel)), animated: true)
|
||||
return
|
||||
}
|
||||
if let viewController = viewController {
|
||||
self.navigationController?.pushViewController(viewController, animated: true)
|
||||
}
|
||||
|
||||
@ -74,6 +74,13 @@ class HomeViewModel: ViewModel, ViewModelType {
|
||||
queryParameter: "group=groupName",
|
||||
image: UIImage(named: "group")
|
||||
),
|
||||
PreviewModel(
|
||||
body: NSLocalizedString("pushNotificationEncryption"),
|
||||
notice: NSLocalizedString("encryptionNotice"),
|
||||
queryParameter: "ciphertext=ciphertext",
|
||||
moreInfo: NSLocalizedString("encryptionSettings"),
|
||||
moreViewModel: CryptoSettingViewModel()
|
||||
),
|
||||
PreviewModel(
|
||||
body: NSLocalizedString("interruptionLevel"),
|
||||
notice: NSLocalizedString("interruptionLevelNotice"),
|
||||
|
||||
@ -169,11 +169,6 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
|
||||
self?.alertMessage(message: message)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
// 点击message中的URL
|
||||
output.urlTap.drive(onNext: { url in
|
||||
Client.shared.openUrl(url: url)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
// 选择群组
|
||||
output.groupFilter
|
||||
.drive(onNext: { [weak self] groupModel in
|
||||
@ -190,7 +185,7 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
|
||||
|
||||
func alertMessage(message: String) {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
let copyAction = UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: { [weak self]
|
||||
let copyAction = UIAlertAction(title: NSLocalizedString("CopyAll"), style: .default, handler: { [weak self]
|
||||
(_: UIAlertAction) -> Void in
|
||||
UIPasteboard.general.string = message
|
||||
self?.showSnackbar(text: NSLocalizedString("Copy"))
|
||||
|
||||
@ -27,7 +27,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
|
||||
var messages: Driver<[MessageSection]>
|
||||
var refreshAction: Driver<MJRefreshAction>
|
||||
var alertMessage: Driver<String>
|
||||
var urlTap: Driver<URL>
|
||||
var groupFilter: Driver<GroupFilterViewModel>
|
||||
var title: Driver<String>
|
||||
}
|
||||
@ -178,17 +177,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
|
||||
}
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
// cell 中点击 url。
|
||||
let urlTap = messagesRelay.flatMapLatest { section -> Observable<String> in
|
||||
if let section = section.first {
|
||||
let taps = section.messages.compactMap { model -> Observable<String> in
|
||||
model.urlTap.asObservable()
|
||||
}
|
||||
return Observable.merge(taps)
|
||||
}
|
||||
return .empty()
|
||||
}
|
||||
.compactMap { URL(string: $0) } // 只处理正确的url
|
||||
|
||||
// 批量删除
|
||||
input.delete.drive(onNext: { [weak self] type in
|
||||
@ -260,7 +248,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
|
||||
messages: messagesRelay.asDriver(onErrorJustReturn: []),
|
||||
refreshAction: refreshAction.asDriver(),
|
||||
alertMessage: alertMessage,
|
||||
urlTap: urlTap.asDriver(onErrorDriveWith: .empty()),
|
||||
groupFilter: groupFilter.asDriver(),
|
||||
title: titleRelay.asDriver()
|
||||
)
|
||||
|
||||
@ -7,9 +7,13 @@
|
||||
//
|
||||
|
||||
import Material
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import RxSwift
|
||||
import UIKit
|
||||
class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel> {
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel>, UIDocumentPickerDelegate {
|
||||
let tableView: UITableView = {
|
||||
let tableView = UITableView()
|
||||
tableView.separatorStyle = .none
|
||||
@ -32,12 +36,93 @@ class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
/// 导入、导出操作枚举
|
||||
enum BackupOrRestoreActionEnum {
|
||||
case export, `import`(data: Data)
|
||||
}
|
||||
|
||||
/// UIDocumentPickerDelegate delegate
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
guard let url = urls.first else { return }
|
||||
let canAccessingResource = url.startAccessingSecurityScopedResource()
|
||||
guard canAccessingResource else { return }
|
||||
|
||||
let fileCoordinator = NSFileCoordinator()
|
||||
let err = NSErrorPointer(nilLiteral: ())
|
||||
fileCoordinator.coordinate(readingItemAt: url, error: err) { url in
|
||||
if let data = try? Data(contentsOf: url) {
|
||||
self.backupOrRestoreActionRelay.accept(.import(data: data))
|
||||
}
|
||||
}
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
/// 导入、导出事件
|
||||
let backupOrRestoreActionRelay = PublishRelay<BackupOrRestoreActionEnum>()
|
||||
|
||||
/// 生成导入导出事件
|
||||
func getBackupOrRestoreAction() -> (Driver<Void>, Driver<Data>) {
|
||||
let backupOrRestoreAction = self.tableView.rx
|
||||
.modelSelected(MessageSettingItem.self)
|
||||
.filter { item in
|
||||
if case MessageSettingItem.backup = item {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
.flatMapLatest { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return Observable<BackupOrRestoreActionEnum>.empty()
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("export"), style: .default, handler: { _ in
|
||||
strongSelf.backupOrRestoreActionRelay.accept(.export)
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("import"), style: .default, handler: { [weak self] _ in
|
||||
if #available(iOS 14.0, *) {
|
||||
let supportedType: [UTType] = [UTType.json]
|
||||
let pickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedType, asCopy: false)
|
||||
pickerViewController.delegate = self
|
||||
self?.present(pickerViewController, animated: true, completion: nil)
|
||||
}
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
|
||||
strongSelf.navigationController?.present(alertController, animated: true, completion: nil)
|
||||
|
||||
return strongSelf.backupOrRestoreActionRelay.asObservable()
|
||||
}
|
||||
|
||||
let backupAction = backupOrRestoreAction
|
||||
.filter { action in
|
||||
if case .export = action { return true } else { return false }
|
||||
}
|
||||
.map { _ in () }
|
||||
.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
let restoreAction = backupOrRestoreAction
|
||||
.compactMap { action in
|
||||
if case let .import(data) = action { return data } else { return nil }
|
||||
}
|
||||
.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
return (backupAction, restoreAction)
|
||||
}
|
||||
|
||||
override func bindViewModel() {
|
||||
let actions = getBackupOrRestoreAction()
|
||||
let output = viewModel.transform(
|
||||
input: MessageSettingsViewModel.Input(
|
||||
itemSelected: self.tableView.rx.modelSelected(MessageSettingItem.self).asDriver(),
|
||||
deviceToken: Client.shared.deviceToken.asDriver()
|
||||
deviceToken: Client.shared.deviceToken.asDriver(),
|
||||
backupAction: actions.0,
|
||||
restoreAction: actions.1,
|
||||
viewDidAppear: self.rx.methodInvoked(#selector(viewDidAppear(_:)))
|
||||
.map { _ in () },
|
||||
archiveSettingRelay: ArchiveSettingRelay.shared.isArchiveRelay
|
||||
)
|
||||
)
|
||||
|
||||
@ -52,6 +137,12 @@ class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel
|
||||
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(iCloudStatusCell.self)") {
|
||||
return cell
|
||||
}
|
||||
case let .backup(viewModel):
|
||||
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(MutableTextCell.self)") as? MutableTextCell {
|
||||
cell.textLabel?.textColor = BKColor.blue.darken1
|
||||
cell.bindViewModel(model: viewModel)
|
||||
return cell
|
||||
}
|
||||
case let .archiveSetting(viewModel):
|
||||
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(ArchiveSettingCell.self)") as? ArchiveSettingCell {
|
||||
cell.bindViewModel(model: viewModel)
|
||||
@ -80,17 +171,47 @@ class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel
|
||||
return UITableViewCell()
|
||||
}
|
||||
|
||||
// 设置项数据源
|
||||
output.settings
|
||||
.drive(tableView.rx.items(dataSource: dataSource))
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 打开 URL 操作
|
||||
output.openUrl.drive { [weak self] url in
|
||||
self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
|
||||
}.disposed(by: rx.disposeBag)
|
||||
|
||||
// 复制 deviceToken 操作
|
||||
output.copyDeviceToken.drive { [weak self] deviceToken in
|
||||
UIPasteboard.general.string = deviceToken
|
||||
self?.showSnackbar(text: NSLocalizedString("Copy"))
|
||||
}.disposed(by: rx.disposeBag)
|
||||
|
||||
// 导出数据
|
||||
output.exportData.drive { [weak self] data in
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let tempDirectoryURL = fileManager.temporaryDirectory
|
||||
let fileName = "bark_messages_\(Date().formatString(format: "yyyy_MM_dd_HH_mm_ss")).json"
|
||||
let linkURL = tempDirectoryURL.appendingPathComponent(fileName)
|
||||
|
||||
do {
|
||||
// 清空temp文件夹
|
||||
try fileManager
|
||||
.contentsOfDirectory(at: tempDirectoryURL, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
|
||||
.forEach { file in
|
||||
try? fileManager.removeItem(atPath: file.path)
|
||||
}
|
||||
// 写入临时文件
|
||||
try data.write(to: linkURL)
|
||||
}
|
||||
catch {
|
||||
// Hope nothing happens
|
||||
}
|
||||
|
||||
let activityController = UIActivityViewController(activityItems: [linkURL], applicationActivities: nil)
|
||||
self?.navigationController?.present(activityController, animated: true, completion: nil)
|
||||
|
||||
}.disposed(by: rx.disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,29 +8,90 @@
|
||||
|
||||
import Foundation
|
||||
import Material
|
||||
import RealmSwift
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import RxSwift
|
||||
import SwiftyJSON
|
||||
|
||||
class MessageSettingsViewModel: ViewModel, ViewModelType {
|
||||
struct Input {
|
||||
var itemSelected: Driver<MessageSettingItem>
|
||||
var deviceToken: Driver<String?>
|
||||
var backupAction: Driver<Void>
|
||||
var restoreAction: Driver<Data>
|
||||
var viewDidAppear: Observable<Void>
|
||||
var archiveSettingRelay: BehaviorRelay<Bool>
|
||||
}
|
||||
|
||||
struct Output {
|
||||
var settings: Driver<[SectionModel<String, MessageSettingItem>]>
|
||||
var openUrl: Driver<URL>
|
||||
var copyDeviceToken: Driver<String>
|
||||
var exportData: Driver<Data>
|
||||
}
|
||||
|
||||
func transform(input: Input) -> Output {
|
||||
|
||||
let restoreSuccess = input
|
||||
.restoreAction
|
||||
.compactMap { data -> Void? in
|
||||
guard let json = try? JSON(data: data), let arr = json.array else {
|
||||
return nil
|
||||
}
|
||||
guard let realm = try? Realm() else {
|
||||
return nil
|
||||
}
|
||||
try? realm.write {
|
||||
for message in arr {
|
||||
guard let id = message["id"].string else {
|
||||
continue
|
||||
}
|
||||
guard let createDate = message["createDate"].int64 else {
|
||||
continue
|
||||
}
|
||||
|
||||
let title = message["title"].string
|
||||
let body = message["body"].string
|
||||
let url = message["url"].string
|
||||
let group = message["group"].string
|
||||
|
||||
let messageObject = Message()
|
||||
messageObject.id = id
|
||||
messageObject.title = title
|
||||
messageObject.body = body
|
||||
messageObject.url = url
|
||||
messageObject.group = group
|
||||
messageObject.createDate = Date(timeIntervalSince1970: TimeInterval(createDate))
|
||||
realm.add(messageObject, update: .modified)
|
||||
}
|
||||
}
|
||||
return ()
|
||||
}.asObservable().share()
|
||||
|
||||
let settings: [MessageSettingItem] = {
|
||||
var settings = [MessageSettingItem]()
|
||||
settings.append(.label(text: "iCloud"))
|
||||
settings.append(.iCloudStatus)
|
||||
settings.append(.label(text: NSLocalizedString("iCloudSync")))
|
||||
settings.append(.archiveSetting(viewModel: ArchiveSettingCellViewModel(on: ArchiveSettingManager.shared.isArchive)))
|
||||
settings.append(.backup(viewModel: MutableTextCellViewModel(
|
||||
title: "\(NSLocalizedString("export"))/\(NSLocalizedString("import"))",
|
||||
text: Observable.merge([restoreSuccess, input.viewDidAppear])
|
||||
.map { _ in
|
||||
if let realm = try? Realm() {
|
||||
return realm.objects(Message.self)
|
||||
.filter("isDeleted != true")
|
||||
.count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
.map { count in
|
||||
"\(count) \(NSLocalizedString("items"))"
|
||||
}
|
||||
.asDriver(onErrorDriveWith: .empty()))
|
||||
))
|
||||
settings.append(.label(text: NSLocalizedString("exportOrImport")))
|
||||
settings.append(.archiveSetting(viewModel: ArchiveSettingCellViewModel(on: input.archiveSettingRelay)))
|
||||
settings.append(.label(text: NSLocalizedString("archiveNote")))
|
||||
|
||||
settings.append(.label(text: NSLocalizedString("info")))
|
||||
@ -65,36 +126,17 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
|
||||
title: NSLocalizedString("faq"),
|
||||
text: nil,
|
||||
textColor: nil,
|
||||
url: URL(string: "https://day.app/2021/06/barkfaq/")))
|
||||
url: URL(string: NSLocalizedString("faqUrl"))))
|
||||
|
||||
settings.append(.spacer(height: 0.5, color: BKColor.grey.lighten4))
|
||||
settings.append(.detail(
|
||||
title: NSLocalizedString("appSC"),
|
||||
title: NSLocalizedString("documentation"),
|
||||
text: nil,
|
||||
textColor: nil,
|
||||
url: URL(string: "https://github.com/Finb/Bark")))
|
||||
|
||||
settings.append(.spacer(height: 0.5, color: BKColor.grey.lighten4))
|
||||
settings.append(.detail(
|
||||
title: NSLocalizedString("backendSC"),
|
||||
text: nil,
|
||||
textColor: nil,
|
||||
url: URL(string: "https://github.com/Finb/bark-server")))
|
||||
url: URL(string: NSLocalizedString("docUrl"))))
|
||||
return settings
|
||||
}()
|
||||
|
||||
settings.compactMap { item -> ArchiveSettingCellViewModel? in
|
||||
if case let MessageSettingItem.archiveSetting(viewModel) = item {
|
||||
return viewModel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
.first?
|
||||
.on
|
||||
.subscribe(onNext: { on in
|
||||
ArchiveSettingManager.shared.isArchive = on
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
let openUrl = input.itemSelected.compactMap { item -> URL? in
|
||||
if case let MessageSettingItem.detail(_, _, _, url) = item {
|
||||
return url
|
||||
@ -112,11 +154,31 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
let exportSuccess = input.backupAction
|
||||
.asObservable()
|
||||
.subscribe(on: ConcurrentDispatchQueueScheduler(qos: .userInitiated))
|
||||
.compactMap { _ in
|
||||
if let realm = try? Realm() {
|
||||
let messages = realm.objects(Message.self)
|
||||
.filter("isDeleted != true")
|
||||
.sorted(byKeyPath: "createDate", ascending: false)
|
||||
|
||||
var arr = [[String: AnyObject]]()
|
||||
for message in messages {
|
||||
arr.append(message.toDictionary())
|
||||
}
|
||||
return try? JSON(arr).rawData(options: JSONSerialization.WritingOptions.prettyPrinted)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return Output(
|
||||
settings: Driver<[SectionModel<String, MessageSettingItem>]>
|
||||
.just([SectionModel(model: "model", items: settings)]),
|
||||
openUrl: openUrl,
|
||||
copyDeviceToken: copyDeviceToken)
|
||||
copyDeviceToken: copyDeviceToken,
|
||||
exportData: exportSuccess.asDriver(onErrorDriveWith: .empty()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +191,8 @@ enum MessageSettingItem {
|
||||
case archiveSetting(viewModel: ArchiveSettingCellViewModel)
|
||||
// 带 详细按钮的 文本cell
|
||||
case detail(title: String?, text: String?, textColor: UIColor?, url: URL?)
|
||||
// 备份还原按钮
|
||||
case backup(viewModel: MutableTextCellViewModel)
|
||||
// deviceToken
|
||||
case deviceToken(viewModel: MutableTextCellViewModel)
|
||||
// 分隔线
|
||||
|
||||
@ -38,7 +38,7 @@ class NewServerViewModel: ViewModel, ViewModelType {
|
||||
let showSnackbar = PublishRelay<String>()
|
||||
|
||||
let notice = input.noticeClick
|
||||
.map { URL(string: "https://day.app/2018/06/bark-server-document/")! }
|
||||
.map { URL(string: NSLocalizedString("deployUrl"))! }
|
||||
.asDriver()
|
||||
|
||||
input.viewDidAppear
|
||||
|
||||
@ -87,6 +87,15 @@ class ServerListViewModel: ViewModel, ViewModelType {
|
||||
.bind(to: showSnackbar)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 对重置的旧 key 发送错误的 deviceToken, 使其失效。
|
||||
resetServer.filter { ($0.2?.count ?? 0) > 0 && $0.0.key.count > 0 }.flatMapLatest {
|
||||
BarkApi.provider
|
||||
.request(.register(address: $0.0.address, key: $0.0.key, devicetoken: "deleted"))
|
||||
.filterResponseError()
|
||||
}
|
||||
.subscribe()
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 发送重置请求
|
||||
let serverReseted = resetServer.filter { ($0.2?.count ?? 0) > 0 }
|
||||
.flatMapLatest { r -> Observable<Result<JSON, ApiError>> in
|
||||
|
||||
@ -59,9 +59,9 @@ class SoundsViewController: BaseViewController<SoundsViewModel> {
|
||||
.bind(to: tableView.rx.items(dataSource: dataSource))
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
output.copyNameAction.drive(onNext: { name in
|
||||
output.copyNameAction.drive(onNext: { [weak self] name in
|
||||
UIPasteboard.general.string = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.navigationController?.showSnackbar(text: NSLocalizedString("Copy"))
|
||||
self?.navigationController?.showSnackbar(text: NSLocalizedString("Copy"))
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
output.playAction.drive(onNext: { url in
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>IntentsRestrictedWhileLocked</key>
|
||||
<array/>
|
||||
<key>IntentsSupported</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.intents-service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).IntentHandler</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,122 +0,0 @@
|
||||
//
|
||||
// IntentHandler.swift
|
||||
// Intent
|
||||
//
|
||||
// Created by huangfeng on 2021/9/26.
|
||||
// Copyright © 2021 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Intents
|
||||
|
||||
// As an example, this class is set up to handle Message intents.
|
||||
// You will want to replace this or add other intents as appropriate.
|
||||
// The intents you wish to handle must be declared in the extension's Info.plist.
|
||||
|
||||
// You can test your example integration by saying things to Siri like:
|
||||
// "Send a message using <myApp>"
|
||||
// "<myApp> John saying hello"
|
||||
// "Search for messages in <myApp>"
|
||||
|
||||
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
|
||||
override func handler(for intent: INIntent) -> Any {
|
||||
// This is the default implementation. If you want different objects to handle different intents,
|
||||
// you can override this and return the handler you want for that particular intent.
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// MARK: - INSendMessageIntentHandling
|
||||
|
||||
// Implement resolution methods to provide additional information about your intent (optional).
|
||||
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
|
||||
if let recipients = intent.recipients {
|
||||
// If no recipients were provided we'll need to prompt for a value.
|
||||
if recipients.count == 0 {
|
||||
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||
return
|
||||
}
|
||||
|
||||
var resolutionResults = [INSendMessageRecipientResolutionResult]()
|
||||
for recipient in recipients {
|
||||
let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
|
||||
switch matchingContacts.count {
|
||||
case 2 ... Int.max:
|
||||
// We need Siri's help to ask user to pick one from the matches.
|
||||
resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)]
|
||||
|
||||
case 1:
|
||||
// We have exactly one matching contact
|
||||
resolutionResults += [INSendMessageRecipientResolutionResult.success(with: recipient)]
|
||||
|
||||
case 0:
|
||||
// We have no contacts matching the description provided
|
||||
resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()]
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
completion(resolutionResults)
|
||||
} else {
|
||||
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||
}
|
||||
}
|
||||
|
||||
func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
|
||||
if let text = intent.content, !text.isEmpty {
|
||||
completion(INStringResolutionResult.success(with: text))
|
||||
} else {
|
||||
completion(INStringResolutionResult.needsValue())
|
||||
}
|
||||
}
|
||||
|
||||
// Once resolution is completed, perform validation on the intent and provide confirmation (optional).
|
||||
|
||||
func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
||||
// Verify user is authenticated and your app is ready to send a message.
|
||||
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||
let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
|
||||
completion(response)
|
||||
}
|
||||
|
||||
// Handle the completed intent (required).
|
||||
|
||||
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
||||
// Implement your application logic to send a message here.
|
||||
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
||||
completion(response)
|
||||
}
|
||||
|
||||
// Implement handlers for each intent you wish to handle. As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.
|
||||
|
||||
// MARK: - INSearchForMessagesIntentHandling
|
||||
|
||||
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
|
||||
// Implement your application logic to find a message that matches the information in the intent.
|
||||
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
||||
// Initialize with found message's attributes
|
||||
response.messages = [INMessage(
|
||||
identifier: "identifier",
|
||||
content: "I am so excited about SiriKit!",
|
||||
dateSent: Date(),
|
||||
sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil),
|
||||
recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)]
|
||||
)]
|
||||
completion(response)
|
||||
}
|
||||
|
||||
// MARK: - INSetMessageAttributeIntentHandling
|
||||
|
||||
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
||||
// Implement your application logic to set the message attribute here.
|
||||
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
||||
completion(response)
|
||||
}
|
||||
}
|
||||
101
Model/Algorithm.swift
Normal file
101
Model/Algorithm.swift
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// Algorithm.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/2/23.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import CryptoSwift
|
||||
import Foundation
|
||||
|
||||
enum Algorithm: String {
|
||||
case aes128 = "AES128"
|
||||
case aes192 = "AES192"
|
||||
case aes256 = "AES256"
|
||||
|
||||
var modes: [String] {
|
||||
switch self {
|
||||
case .aes128, .aes192, .aes256:
|
||||
return ["CBC", "ECB"]
|
||||
}
|
||||
}
|
||||
|
||||
var paddings: [String] {
|
||||
switch self {
|
||||
case .aes128, .aes192, .aes256:
|
||||
return ["pkcs7"]
|
||||
}
|
||||
}
|
||||
|
||||
var keyLenght: Int {
|
||||
switch self {
|
||||
case .aes128:
|
||||
return 16
|
||||
case .aes192:
|
||||
return 24
|
||||
case .aes256:
|
||||
return 32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CryptoSettingFields: Codable {
|
||||
let algorithm: String
|
||||
let mode: String
|
||||
let padding: String
|
||||
let key: String?
|
||||
var iv: String?
|
||||
}
|
||||
|
||||
struct AESCryptoModel {
|
||||
let key: String
|
||||
let mode: BlockMode
|
||||
let padding: Padding
|
||||
let aes: AES
|
||||
init(cryptoFields: CryptoSettingFields) throws {
|
||||
guard let algorithm = Algorithm(rawValue: cryptoFields.algorithm) else {
|
||||
throw "Invalid algorithm"
|
||||
}
|
||||
guard let key = cryptoFields.key else {
|
||||
throw "Key is missing"
|
||||
}
|
||||
|
||||
guard algorithm.keyLenght == key.count else {
|
||||
throw String(format: NSLocalizedString("enterKey"), algorithm.keyLenght)
|
||||
}
|
||||
|
||||
var iv = ""
|
||||
if ["CBC"].contains(cryptoFields.mode) {
|
||||
if let ivField = cryptoFields.iv, ivField.count == 16 {
|
||||
iv = ivField
|
||||
}
|
||||
else {
|
||||
throw NSLocalizedString("enterIv")
|
||||
}
|
||||
}
|
||||
|
||||
let mode: BlockMode
|
||||
switch cryptoFields.mode {
|
||||
case "CBC":
|
||||
mode = CBC(iv: iv.bytes)
|
||||
case "ECB":
|
||||
mode = ECB()
|
||||
default:
|
||||
throw "Invalid Mode"
|
||||
}
|
||||
|
||||
self.key = key
|
||||
self.mode = mode
|
||||
self.padding = Padding.pkcs7
|
||||
self.aes = try AES(key: key.bytes, blockMode: self.mode, padding: self.padding)
|
||||
}
|
||||
|
||||
func encrypt(text: String) throws -> String {
|
||||
return try aes.encrypt(Array(text.utf8)).toBase64()
|
||||
}
|
||||
|
||||
func decrypt(ciphertext: String) throws -> String {
|
||||
return String(data: Data(try aes.decrypt(Array(base64: ciphertext))), encoding: .utf8) ?? ""
|
||||
}
|
||||
}
|
||||
40
Model/Object+Dictionary.swift
Normal file
40
Model/Object+Dictionary.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Object+Dictionary.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/10/20.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RealmSwift
|
||||
|
||||
extension Object {
|
||||
func toDictionary() -> [String: AnyObject] {
|
||||
let properties = self.objectSchema.properties.map { $0.name }
|
||||
var dicProps = [String: AnyObject]()
|
||||
for (key, value) in self.dictionaryWithValues(forKeys: properties) {
|
||||
if let value = value as? ListBase {
|
||||
dicProps[key] = value.toArray1() as AnyObject
|
||||
} else if let value = value as? Object {
|
||||
dicProps[key] = value.toDictionary() as AnyObject
|
||||
} else if let value = value as? Date {
|
||||
dicProps[key] = Int64(value.timeIntervalSince1970) as AnyObject
|
||||
} else {
|
||||
dicProps[key] = value as AnyObject
|
||||
}
|
||||
}
|
||||
return dicProps
|
||||
}
|
||||
}
|
||||
|
||||
extension ListBase {
|
||||
func toArray1() -> [AnyObject] {
|
||||
var _toArray = [AnyObject]()
|
||||
for i in 0 ..< self._rlmArray.count {
|
||||
let obj = unsafeBitCast(self._rlmArray[i], to: Object.self)
|
||||
_toArray.append(obj.toDictionary() as AnyObject)
|
||||
}
|
||||
return _toArray
|
||||
}
|
||||
}
|
||||
@ -10,8 +10,10 @@ import Intents
|
||||
import Kingfisher
|
||||
import MobileCoreServices
|
||||
import RealmSwift
|
||||
import SwiftyJSON
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
lazy var realm: Realm? = {
|
||||
@ -232,14 +234,75 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
|
||||
func decrypt(ciphertext: String, iv: String? = nil) throws -> [String: Any] {
|
||||
guard var fields = CryptoSettingManager.shared.fields else {
|
||||
throw "No encryption key set"
|
||||
}
|
||||
if let iv = iv {
|
||||
// Support using specified IV parameter for decryption
|
||||
fields.iv = iv
|
||||
}
|
||||
|
||||
let aes = try AESCryptoModel(cryptoFields: fields)
|
||||
|
||||
let json = try aes.decrypt(ciphertext: ciphertext)
|
||||
|
||||
guard let data = json.data(using: .utf8), let map = JSON(data).dictionaryObject else {
|
||||
throw "JSON parsing failed"
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> ()) {
|
||||
guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
|
||||
let userInfo = bestAttemptContent.userInfo
|
||||
|
||||
|
||||
var userInfo = bestAttemptContent.userInfo
|
||||
// 如果是加密推送,则使用密文配置 bestAttemptContent
|
||||
if let ciphertext = userInfo["ciphertext"] as? String {
|
||||
do {
|
||||
var map = try decrypt(ciphertext: ciphertext, iv: userInfo["iv"] as? String)
|
||||
for (key, val) in map {
|
||||
// 将key重写为小写
|
||||
map[key.lowercased()] = val
|
||||
}
|
||||
|
||||
var alert = [String: Any]()
|
||||
if let title = map["title"] as? String {
|
||||
bestAttemptContent.title = title
|
||||
alert["title"] = title
|
||||
}
|
||||
if let body = map["body"] as? String {
|
||||
bestAttemptContent.body = body
|
||||
alert["body"] = body
|
||||
}
|
||||
if let group = map["group"] as? String {
|
||||
bestAttemptContent.threadIdentifier = group
|
||||
}
|
||||
if var sound = map["sound"] as? String {
|
||||
if !sound.hasSuffix(".caf") {
|
||||
sound = "\(sound).caf"
|
||||
}
|
||||
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: sound))
|
||||
}
|
||||
if let badge = map["badge"] as? Int {
|
||||
bestAttemptContent.badge = badge as NSNumber
|
||||
}
|
||||
|
||||
map["aps"] = ["alert": alert]
|
||||
userInfo = map
|
||||
bestAttemptContent.userInfo = userInfo
|
||||
}
|
||||
catch {
|
||||
bestAttemptContent.body = "Decryption Failed"
|
||||
bestAttemptContent.userInfo = ["aps": ["alert": ["body": bestAttemptContent.body]]]
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 通知中断级别
|
||||
if #available(iOSApplicationExtension 15.0, *) {
|
||||
if let level = userInfo["level"] as? String {
|
||||
|
||||
17
Podfile
17
Podfile
@ -15,6 +15,8 @@ def pods
|
||||
pod 'DeviceKit'
|
||||
pod 'DefaultsKit', :git => 'https://github.com/nmdias/DefaultsKit'
|
||||
pod 'IceCream'
|
||||
pod 'CryptoSwift'
|
||||
pod 'IQKeyboardManagerSwift'
|
||||
|
||||
pod 'RxSwift'
|
||||
pod 'RxCocoa'
|
||||
@ -25,6 +27,7 @@ def pods
|
||||
pod 'MJRefresh'
|
||||
pod 'Kingfisher'
|
||||
pod 'MercariQRScanner', :git => 'https://github.com/Finb/QRScanner'
|
||||
pod 'DropDown'
|
||||
end
|
||||
|
||||
target 'Bark' do
|
||||
@ -40,4 +43,18 @@ end
|
||||
target 'NotificationServiceExtension' do
|
||||
pod 'IceCream'
|
||||
pod 'Kingfisher'
|
||||
pod 'CryptoSwift'
|
||||
pod 'SwiftyJSON'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 13.0
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||
end
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
16
Podfile.lock
16
Podfile.lock
@ -1,11 +1,14 @@
|
||||
PODS:
|
||||
- Alamofire (5.4.4)
|
||||
- CryptoSwift (1.6.0)
|
||||
- DefaultsKit (0.2.0)
|
||||
- DeviceKit (4.5.0)
|
||||
- Differentiator (5.0.0)
|
||||
- DropDown (2.3.13)
|
||||
- FDFullscreenPopGesture (1.1)
|
||||
- IceCream (2.0.4):
|
||||
- RealmSwift (< 10.8.0)
|
||||
- IQKeyboardManagerSwift (6.5.10)
|
||||
- Kingfisher (6.3.1)
|
||||
- KVOController (1.2.0)
|
||||
- Material (3.1.8):
|
||||
@ -48,10 +51,13 @@ PODS:
|
||||
- SwiftyJSON (5.0.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- CryptoSwift
|
||||
- DefaultsKit (from `https://github.com/nmdias/DefaultsKit`)
|
||||
- DeviceKit
|
||||
- DropDown
|
||||
- FDFullscreenPopGesture
|
||||
- IceCream
|
||||
- IQKeyboardManagerSwift
|
||||
- Kingfisher
|
||||
- KVOController
|
||||
- Material
|
||||
@ -71,10 +77,13 @@ DEPENDENCIES:
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Alamofire
|
||||
- CryptoSwift
|
||||
- DeviceKit
|
||||
- Differentiator
|
||||
- DropDown
|
||||
- FDFullscreenPopGesture
|
||||
- IceCream
|
||||
- IQKeyboardManagerSwift
|
||||
- Kingfisher
|
||||
- KVOController
|
||||
- Material
|
||||
@ -110,11 +119,14 @@ CHECKOUT OPTIONS:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9
|
||||
CryptoSwift: 562f8eceb40e80796fffc668b0cad9313284cfa6
|
||||
DefaultsKit: 6c767941b2c3fe34c4d70e300e6c3b80c94964dd
|
||||
DeviceKit: b68cded46afdaefc5d2253d631d441ba7049a7fe
|
||||
Differentiator: e8497ceab83c1b10ca233716d547b9af21b9344d
|
||||
DropDown: 8a2116376c1981888557f72ec2ffc9a5e0e456ec
|
||||
FDFullscreenPopGesture: a8a620179e3d9c40e8e00256dcee1c1a27c6d0f0
|
||||
IceCream: 717d516a1c634eba8eaa8ce7d3d7bc5f7e40c2fa
|
||||
IQKeyboardManagerSwift: 52962c76ab33532f15ad9f3ff4e5715eda5335bb
|
||||
Kingfisher: 016c8b653a35add51dd34a3aba36b580041acc74
|
||||
KVOController: d72ace34afea42468329623b3379ab3cd1d286b6
|
||||
Material: a2a3f400a3b549d53ef89e56c58c4535b29db387
|
||||
@ -135,6 +147,6 @@ SPEC CHECKSUMS:
|
||||
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
|
||||
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
|
||||
|
||||
PODFILE CHECKSUM: f2443e42f5062c17de4e7a770482b083d2659182
|
||||
PODFILE CHECKSUM: 60eda42686b4893abd7ff19557301ef40fca0dff
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.11.3
|
||||
|
||||
146
README.en.md
Normal file
146
README.en.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Bark
|
||||
|
||||

|
||||
|
||||
[Bark](https://github.com/Finb/Bark) is an open-source push notifications app that respects your privacy. You can host your own server, or use the free public service, to send real-time alerts to your iPhone. Everything stays between you and your server (plus Apple). Bark supports webhooks and a simple HTTP API.
|
||||
|
||||
This repository contains the source code for the iOS app. For the backend, [bark-server](https://github.com/Finb/bark-server) is written in Go, and supports Docker and serverless functions.
|
||||
|
||||
## Send a Push Notification
|
||||
|
||||
### API v2
|
||||
|
||||
**Bark is switching to to API v2, which supports webhooks. See [API_V2.md](https://github.com/Finb/bark-server/blob/master/docs/API_V2.md).**
|
||||
|
||||
### API v1
|
||||
|
||||
The Bark app supports both API v1 and v2, but the UI currently only showcases v1. API v1 supports both HTTP GET or POST requests with the following format.
|
||||
|
||||
```
|
||||
https://{server}/{key}/{body}
|
||||
https://{server}/{key}/{title}/{body}
|
||||
```
|
||||
|
||||
API v1 also supports optional query strings.
|
||||
|
||||
`automaticallyCopy` (boolean)
|
||||
|
||||
```
|
||||
//Automatically copy the body text of a notification (iOS 14.4 and below)
|
||||
https://api.day.app/key/body?automaticallyCopy=1
|
||||
```
|
||||
|
||||
`badge` (integer)
|
||||
|
||||
```
|
||||
//Specify the badge count on the app icon
|
||||
https://api.day.app/key/body?badge=3
|
||||
```
|
||||
|
||||
`copy` (string)
|
||||
|
||||
```
|
||||
//Specify clipboard content when copying
|
||||
https://api.day.app/key/body?copy=1234
|
||||
```
|
||||
|
||||
`group` (string)
|
||||
|
||||
```
|
||||
//Let iOS sort notifications by groups
|
||||
https://api.day.app/key/body?group=groupName
|
||||
```
|
||||
|
||||
`icon` (string)
|
||||
|
||||
```
|
||||
//Display a custom icon on a notification (iOS 15 and above)
|
||||
https://api.day.app/key/body?icon=http://day.app/assets/images/avatar.jpg
|
||||
```
|
||||
|
||||
`isArchive` (boolean)
|
||||
|
||||
```
|
||||
//Archive a notification if 1, prevent archiving if 0, default settings will apply if unset
|
||||
https://api.day.app/key/body?isArchive=1
|
||||
```
|
||||
|
||||
`level` (boolean)
|
||||
|
||||
```
|
||||
//Set importance and delivery timing of a notification
|
||||
//active (default): presents the notification immediately, lights up the screen, and can play a sound
|
||||
//timeSensitive: similar to active, but can break through system controls such as Notification Summary and Focus
|
||||
//passive: adds the notification to the notification list without lighting up the screen or playing a sound
|
||||
https://api.day.app/key/body?level=timeSensitive
|
||||
```
|
||||
|
||||
`sound` (string)
|
||||
|
||||
```
|
||||
//Select an alert sound for a notification
|
||||
//Available sounds are located in the 'Sounds' folder and can be previewed in the app
|
||||
https://api.day.app/key/body?sound=alarm
|
||||
```
|
||||
|
||||
`url` (string)
|
||||
|
||||
```
|
||||
//Open an URL by clicking on a notification
|
||||
https://api.day.app/key/body?automaticallyCopy=1
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
* Time Sensitive notifications don’t work.
|
||||
|
||||
Try restarting your device.
|
||||
* I can’t archive or copy my notifications.
|
||||
|
||||
Try restarting your device. Sometimes Apple’s notification extension ([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension)) fails to run. Consequently Bark can’t excute the code to do so.
|
||||
* The auto copy function doesn’t work.
|
||||
|
||||
Since iOS 14.5, Apple has tightened clipboard security, and copying is no longer possible without user intervention. You can pull down, long press or open the notification to trigger the auto copy function, or use the system popup button to copy.
|
||||
* Are there any limits on the number of notifications I can receive?
|
||||
|
||||
There aren’t any limitations. However, if you make a massive amount (> 100,000) of bad requests (e.g., 500 or 404 resulted from bad formatting), your IP will be banned.
|
||||
* How do I open the Message History tab on startup?
|
||||
|
||||
Bark will open on the same tab it was closed on. Close Bark on the Message History tab.
|
||||
* Does Bark support POST requests?
|
||||
|
||||
Bark supports GET and POST requests with the same set of parameters. If you want to use webhooks with JSON, you must use API v2. See [API_V2.md](https://github.com/Finb/bark-server/blob/master/docs/API_V2.md).
|
||||
* My notifications failed to push/got messed up when there are special characters.
|
||||
|
||||
You need to use URL encoding to convert the special chracters, or the Bark server might not be able to properly interprete the request. Production-level libraries usually handle special characters automatically.
|
||||
* How can I make sure my notifications stay private?
|
||||
|
||||
This is how your notification reaches you with Bark:
|
||||
Sender → Bark Server → Apple Push Notification Service → Your iPhone → Bark Client
|
||||
The Bark server and client are the two attack surfaces that we control. You can mitigate the risks by:
|
||||
1. Deploy your own Bark server with [the open-source backend](https://github.com/Finb/bark-server).
|
||||
2. Make sure the Bark app has not been tampered.
|
||||
|
||||
Bark uses GitHub Actions to build and publish itself to the App Store. You can verify whether the ‘run_id’ in the app matches the one on GitHub to ensure the app was built from its original codebase.
|
||||
|
||||
You can also find build configurations, version and build numbers in the run log. The version and build numbers must be unique to be accepted by the App Store. Therefore you can verify whether the version and build numbers of your app match the ones in the run log.
|
||||
|
||||
## Community Ports
|
||||
|
||||
Push from Chrome Extension: [Bark-Chrome-Extension](https://github.com/xlvecle/Bark-Chrome-Extension)
|
||||
|
||||
Push from Web with scheduled tasks: [Bark Assisatant](https://api.ihint.me/bark.html)
|
||||
|
||||
Push from Windows: [BarkHelper](https://github.com/HsuDan/BarkHelper)
|
||||
|
||||
Push from CLI: [bark-cli](https://github.com/JasonkayZK/bark-cli)
|
||||
|
||||
Quicker Action: https://getquicker.net/Sharedaction?code=e927d844-d212-4428-758d-08d69de12a3b
|
||||
|
||||
Bark for Wox: [Wox.Plugin.Bark](https://github.com/Zeroto521/Wox.Plugin.Bark)
|
||||
|
||||
java-bark-server: [https://gitee.com/hotlcc/java-bark-server](https://gitee.com/hotlcc/java-bark-server)
|
||||
|
||||
Python for Bark: [barknotificator
|
||||
](https://github.com/funny-cat-happy/barknotificator)
|
||||
|
||||
35
README.md
35
README.md
@ -1,5 +1,5 @@
|
||||
## 常见问题
|
||||
[https://day.app/2021/06/barkfaq/](https://day.app/2021/06/barkfaq/)
|
||||
## 使用文档
|
||||
[https://bark.day.app](https://bark.day.app)
|
||||
|
||||
## 问题反馈 Telegram 群
|
||||
[Bark反馈群](https://t.me/joinchat/OsCbLzovUAE0YjY1)
|
||||
@ -103,26 +103,8 @@ https://api.day.app/yourkey/时效性通知?level=timeSensitive
|
||||
## 跨平台的命令行应用
|
||||
[https://github.com/JasonkayZK/bark-cli](https://github.com/JasonkayZK/bark-cli)
|
||||
|
||||
## bark-jssdk
|
||||
> 几行代码,用 nodejs 使用 bark sdk
|
||||
|
||||
[https://github.com/afeiship/bark-jssdk](https://github.com/afeiship/bark-jssdk)
|
||||
|
||||
```js
|
||||
import BarkJssdk from '@jswork/bark-jssdk';
|
||||
|
||||
// use instance
|
||||
const sdk = new BarkJssdk({ sdkKey: 'YOUR_SDK_KEY'});
|
||||
// notify
|
||||
sdk.notify({ title: 'Tips', body: 'New message comming.' });
|
||||
|
||||
// OR use BarkJssdk.notify directly
|
||||
BarkJssdk.notify({
|
||||
sdkKey: 'YOUR_SDK_KEY',
|
||||
title: 'Tips',
|
||||
body: 'New message comming.'
|
||||
});
|
||||
```
|
||||
## GitHub Actions
|
||||
[https://github.com/harryzcy/action-bark](https://github.com/harryzcy/action-bark)
|
||||
|
||||
## Quicker 动作
|
||||
使用 Quicker 软件在 Windows 上将选中文字一键推送到iPhone,支持打开URL和自动复制推送内容
|
||||
@ -130,3 +112,12 @@ BarkJssdk.notify({
|
||||
|
||||
## Bark for Wox
|
||||
[https://github.com/Zeroto521/Wox.Plugin.Bark](https://github.com/Zeroto521/Wox.Plugin.Bark)
|
||||
|
||||
## bark-jssdk
|
||||
[https://github.com/afeiship/bark-jssdk](https://github.com/afeiship/bark-jssdk)
|
||||
|
||||
## java-bark-server
|
||||
[https://gitee.com/hotlcc/java-bark-server](https://gitee.com/hotlcc/java-bark-server)
|
||||
|
||||
## Python for Bark
|
||||
[barknotificator](https://github.com/funny-cat-happy/barknotificator)
|
||||
|
||||
@ -10,8 +10,8 @@ import Foundation
|
||||
import RxCocoa
|
||||
class ArchiveSettingCellViewModel: ViewModel {
|
||||
var on: BehaviorRelay<Bool>
|
||||
init(on: Bool) {
|
||||
self.on = BehaviorRelay<Bool>(value: on)
|
||||
init(on: BehaviorRelay<Bool>) {
|
||||
self.on = on
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
48
View/BKDropDownCell.swift
Normal file
48
View/BKDropDownCell.swift
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// BKDropDownCellTableViewCell.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/2/9.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import DropDown
|
||||
import UIKit
|
||||
|
||||
class BKDropDownCell: DropDownCell {
|
||||
|
||||
@IBOutlet var selectBackgroundView: UIView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
self.backgroundColor = BKColor.white
|
||||
self.selectBackgroundView.layer.cornerRadius = 10
|
||||
self.selectBackgroundView.clipsToBounds = true
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
|
||||
let executeSelection: () -> Void = { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
|
||||
let selectedBackgroundColor = BKColor.grey.lighten5
|
||||
if selected {
|
||||
self.selectBackgroundView.backgroundColor = selectedBackgroundColor
|
||||
self.optionLabel.textColor = BKColor.grey.darken4
|
||||
} else {
|
||||
self.selectBackgroundView.backgroundColor = .clear
|
||||
self.optionLabel.textColor = BKColor.grey.darken3
|
||||
}
|
||||
}
|
||||
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
executeSelection()
|
||||
})
|
||||
} else {
|
||||
executeSelection()
|
||||
}
|
||||
|
||||
accessibilityTraits = selected ? .selected : .none
|
||||
}
|
||||
}
|
||||
56
View/BKDropDownCell.xib
Normal file
56
View/BKDropDownCell.xib
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="Ov8-GI-Rat" customClass="BKDropDownCell" customModule="Bark" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ov8-GI-Rat" id="JkW-au-90U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Be1-Hg-cfz" userLabel="Select Background View">
|
||||
<rect key="frame" x="6" y="4" width="381" height="54"/>
|
||||
<viewLayoutGuide key="safeArea" id="GJ2-4o-aoF"/>
|
||||
<color key="backgroundColor" systemColor="linkColor"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2lm-va-4a1">
|
||||
<rect key="frame" x="16" y="5" width="361" height="52"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="2lm-va-4a1" secondAttribute="bottom" constant="5" id="A61-Or-MrS"/>
|
||||
<constraint firstItem="Be1-Hg-cfz" firstAttribute="leading" secondItem="JkW-au-90U" secondAttribute="leading" constant="6" id="Cl3-sY-bfR"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Be1-Hg-cfz" secondAttribute="trailing" constant="6" id="M5z-T7-Vbz"/>
|
||||
<constraint firstItem="Be1-Hg-cfz" firstAttribute="top" secondItem="JkW-au-90U" secondAttribute="top" constant="4" id="YzQ-6y-cJa"/>
|
||||
<constraint firstItem="2lm-va-4a1" firstAttribute="top" secondItem="JkW-au-90U" secondAttribute="top" constant="5" id="cix-Ub-eyW"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2lm-va-4a1" secondAttribute="trailing" constant="16" id="fmp-HQ-Icr"/>
|
||||
<constraint firstItem="2lm-va-4a1" firstAttribute="leading" secondItem="JkW-au-90U" secondAttribute="leading" constant="16" id="kdu-uI-TlX"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Be1-Hg-cfz" secondAttribute="bottom" constant="4" id="twp-ah-AEA"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="optionLabel" destination="2lm-va-4a1" id="try-1f-LgE"/>
|
||||
<outlet property="selectBackgroundView" destination="Be1-Hg-cfz" id="mpw-MN-DAU"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="46.564885496183201" y="-64.08450704225352"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<systemColor name="linkColor">
|
||||
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
101
View/BorderTextField.swift
Normal file
101
View/BorderTextField.swift
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// BorderTextField.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/2/6.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class InsetTextField: UITextField {
|
||||
var insets = UIEdgeInsets.zero
|
||||
|
||||
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
let bounds = super.textRect(forBounds: bounds)
|
||||
return bounds.inset(by: insets)
|
||||
}
|
||||
|
||||
override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
let bounds = super.textRect(forBounds: bounds)
|
||||
return bounds.inset(by: insets)
|
||||
}
|
||||
|
||||
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||
let bounds = super.textRect(forBounds: bounds)
|
||||
return bounds.inset(by: insets)
|
||||
}
|
||||
}
|
||||
|
||||
class BorderTextField: InsetTextField {
|
||||
var isSelecting: Bool = true {
|
||||
didSet {
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
if self.isSelecting {
|
||||
self.backgroundView.borderColor = BKColor.blue.darken5
|
||||
self.backgroundView.shadowColor = BKColor.blue.darken5
|
||||
self.backgroundView.layer.shadowOpacity = 0.3
|
||||
}
|
||||
else {
|
||||
self.backgroundView.borderColor = BKColor.grey.lighten2
|
||||
self.backgroundView.shadowColor = BKColor.grey.lighten2
|
||||
self.backgroundView.layer.shadowOpacity = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = BKColor.white
|
||||
view.isUserInteractionEnabled = false
|
||||
view.cornerRadiusPreset = .cornerRadius3
|
||||
view.shadowColor = BKColor.grey.lighten2
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
view.layer.shadowRadius = 2
|
||||
view.layer.shadowOpacity = 0
|
||||
view.borderColor = BKColor.grey.lighten2
|
||||
view.borderWidthPreset = .border2
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
init(title: String? = nil) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.insets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
|
||||
|
||||
self.textColor = BKColor.grey.darken3
|
||||
self.font = UIFont.systemFont(ofSize: 14)
|
||||
self.textAlignment = .left
|
||||
|
||||
self.insertSubview(backgroundView, at: 0)
|
||||
backgroundView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
self.delegate = self
|
||||
}
|
||||
override var placeholder: String? {
|
||||
didSet{
|
||||
self.attributedPlaceholder = NSAttributedString(string: placeholder ?? "" , attributes: [
|
||||
.font: self.font ?? UIFont.systemFont(ofSize: 14),
|
||||
.foregroundColor: BKColor.grey.darken1
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension BorderTextField: UITextFieldDelegate {
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
self.isSelecting = true
|
||||
}
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
self.isSelecting = false
|
||||
}
|
||||
}
|
||||
137
View/DropBoxView.swift
Normal file
137
View/DropBoxView.swift
Normal file
@ -0,0 +1,137 @@
|
||||
//
|
||||
// DropBoxView.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/2/2.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import DropDown
|
||||
import UIKit
|
||||
import RxCocoa
|
||||
import RxSwift
|
||||
|
||||
class DropBoxView: UIView {
|
||||
|
||||
let valueLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = UIFont.systemFont(ofSize: 14)
|
||||
label.textColor = BKColor.grey.darken3
|
||||
return label
|
||||
}()
|
||||
|
||||
let dropIconView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.image = UIImage(named: "baseline_keyboard_arrow_down_black_24pt")?.withRenderingMode(.alwaysTemplate)
|
||||
imageView.tintColor = BKColor.grey.lighten2
|
||||
return imageView
|
||||
}()
|
||||
|
||||
var isSelecting: Bool = true {
|
||||
didSet {
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
if self.isSelecting {
|
||||
self.borderColor = BKColor.blue.darken5
|
||||
self.shadowColor = BKColor.blue.darken5
|
||||
self.layer.shadowOpacity = 0.3
|
||||
}
|
||||
else {
|
||||
self.borderColor = BKColor.grey.lighten2
|
||||
self.shadowColor = BKColor.grey.lighten2
|
||||
self.layer.shadowOpacity = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var values: [String] {
|
||||
didSet {
|
||||
self.currentValue = values.first
|
||||
}
|
||||
}
|
||||
|
||||
var currentValue: String? {
|
||||
didSet {
|
||||
self.valueLabel.text = currentValue
|
||||
self.currentValueChanged?(self.currentValue)
|
||||
}
|
||||
}
|
||||
|
||||
var currentValueChanged: ((String?) -> Void)?
|
||||
|
||||
init(values: [String]) {
|
||||
self.values = values
|
||||
super.init(frame: CGRect.zero)
|
||||
self.backgroundColor = BKColor.white
|
||||
|
||||
self.borderColor = BKColor.grey.lighten2
|
||||
self.borderWidthPreset = .border2
|
||||
self.cornerRadiusPreset = .cornerRadius3
|
||||
self.shadowColor = BKColor.grey.lighten2
|
||||
self.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
self.layer.shadowRadius = 2
|
||||
self.layer.shadowOpacity = 0
|
||||
|
||||
addSubview(valueLabel)
|
||||
addSubview(dropIconView)
|
||||
|
||||
self.dropIconView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalTo(-6)
|
||||
}
|
||||
self.valueLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalTo(16)
|
||||
}
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
|
||||
|
||||
defer {
|
||||
self.currentValue = self.values.first
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func tap() {
|
||||
let dropDown = DropDown(anchorView: self)
|
||||
dropDown.cellNib = UINib(nibName: "BKDropDownCell", bundle: Bundle(for: BKDropDownCell.self))
|
||||
dropDown.cellHeight = 50
|
||||
dropDown.cornerRadius = 10
|
||||
dropDown.clipsToBounds = true
|
||||
dropDown.bottomOffset = CGPoint(x: 0, y: 50)
|
||||
|
||||
dropDown.dataSource = self.values
|
||||
|
||||
dropDown.selectionAction = { [weak self] _, str in
|
||||
self?.currentValue = str
|
||||
self?.isSelecting = false
|
||||
}
|
||||
dropDown.cancelAction = { [weak self] in
|
||||
self?.isSelecting = false
|
||||
}
|
||||
self.isSelecting = true
|
||||
dropDown.show()
|
||||
}
|
||||
}
|
||||
|
||||
extension Reactive where Base: DropBoxView {
|
||||
|
||||
var currentValueChanged: ControlEvent<String?> {
|
||||
let source = Observable<String?>.create { [weak control = self.base] observer -> Disposable in
|
||||
MainScheduler.ensureExecutingOnScheduler()
|
||||
guard let control = control else {
|
||||
observer.onCompleted()
|
||||
return Disposables.create()
|
||||
}
|
||||
control.currentValueChanged = { value in
|
||||
observer.onNext(value)
|
||||
}
|
||||
return Disposables.create()
|
||||
}
|
||||
return ControlEvent(events: source)
|
||||
}
|
||||
}
|
||||
41
View/GesturePassTextView.swift
Normal file
41
View/GesturePassTextView.swift
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// GesturePassTextView.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/7/26.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class GesturePassTextView: UITextView {
|
||||
var superCell: UITableViewCell? = nil
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let cell = superCell {
|
||||
cell.touchesBegan(touches, with: event)
|
||||
return
|
||||
}
|
||||
super.touchesBegan(touches, with: event)
|
||||
}
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let cell = superCell {
|
||||
cell.touchesMoved(touches, with: event)
|
||||
return
|
||||
}
|
||||
super.touchesMoved(touches, with: event)
|
||||
}
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let cell = superCell {
|
||||
cell.touchesEnded(touches, with: event)
|
||||
return
|
||||
}
|
||||
super.touchesEnded(touches, with: event)
|
||||
}
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let cell = superCell {
|
||||
cell.touchesCancelled(touches, with: event)
|
||||
return
|
||||
}
|
||||
super.touchesCancelled(touches, with: event)
|
||||
}
|
||||
}
|
||||
62
View/GradientButton.swift
Normal file
62
View/GradientButton.swift
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// UIView+Gradient.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/2/10.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
|
||||
|
||||
enum GradientOrientation {
|
||||
case topRightBottomLeft
|
||||
case topLeftBottomRight
|
||||
case horizontal
|
||||
case vertical
|
||||
|
||||
var startPoint: CGPoint {
|
||||
return points.startPoint
|
||||
}
|
||||
|
||||
var endPoint: CGPoint {
|
||||
return points.endPoint
|
||||
}
|
||||
|
||||
var points: GradientPoints {
|
||||
switch self {
|
||||
case .topRightBottomLeft:
|
||||
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
|
||||
case .topLeftBottomRight:
|
||||
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
|
||||
case .horizontal:
|
||||
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
|
||||
case .vertical:
|
||||
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GradientButton: UIButton {
|
||||
lazy var gradient: CAGradientLayer = {
|
||||
let gradient = CAGradientLayer()
|
||||
return gradient
|
||||
}()
|
||||
|
||||
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
|
||||
gradient.frame = self.bounds
|
||||
gradient.colors = colours.map { $0.cgColor }
|
||||
gradient.startPoint = orientation.startPoint
|
||||
gradient.endPoint = orientation.endPoint
|
||||
|
||||
if gradient.superlayer == nil {
|
||||
self.layer.insertSublayer(gradient, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
gradient.frame = self.bounds
|
||||
}
|
||||
}
|
||||
69
View/HUD.swift
Normal file
69
View/HUD.swift
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// HUD.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2023/3/6.
|
||||
// Copyright © 2023 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
class BarkProgressHUD: SVProgressHUD {
|
||||
override class func displayDuration(for string: String?) -> TimeInterval {
|
||||
return min(Double((string ?? "").utf8.count) * 0.06 + 0.5, 5.0)
|
||||
}
|
||||
}
|
||||
|
||||
open class ProgressHUD: NSObject {
|
||||
open class func show() {
|
||||
BarkProgressHUD.show()
|
||||
}
|
||||
|
||||
open class func showWithClearMask() {
|
||||
BarkProgressHUD.show()
|
||||
}
|
||||
|
||||
open class func dismiss() {
|
||||
BarkProgressHUD.dismiss()
|
||||
}
|
||||
|
||||
open class func showWithStatus(_ status: String!) {
|
||||
BarkProgressHUD.show(withStatus: status)
|
||||
}
|
||||
|
||||
open class func success(_ status: String!) {
|
||||
BarkProgressHUD.showSuccess(withStatus: status)
|
||||
}
|
||||
|
||||
open class func error(_ status: String!) {
|
||||
BarkProgressHUD.showError(withStatus: status)
|
||||
}
|
||||
|
||||
open class func inform(_ status: String!) {
|
||||
BarkProgressHUD.showInfo(withStatus: status)
|
||||
}
|
||||
}
|
||||
|
||||
public func HUDSuccess(_ status: String?) {
|
||||
ProgressHUD.success(status ?? "")
|
||||
}
|
||||
|
||||
public func HUDError(_ status: String?) {
|
||||
ProgressHUD.error(status ?? "")
|
||||
}
|
||||
|
||||
public func HUDInform(_ status: String?) {
|
||||
ProgressHUD.inform(status ?? "")
|
||||
}
|
||||
|
||||
public func HUDShow() {
|
||||
ProgressHUD.show()
|
||||
}
|
||||
|
||||
public func HUDShowWithStatus(_ status: String!) {
|
||||
ProgressHUD.showWithStatus(status)
|
||||
}
|
||||
|
||||
public func HUDDismiss() {
|
||||
ProgressHUD.dismiss()
|
||||
}
|
||||
@ -7,7 +7,9 @@
|
||||
//
|
||||
|
||||
import Material
|
||||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
|
||||
let backgroundPanel: UIView = {
|
||||
let view = UIView()
|
||||
@ -17,29 +19,15 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
|
||||
return view
|
||||
}()
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = RobotoFont.medium(with: 16)
|
||||
label.textColor = BKColor.grey.darken4
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
let bodyLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
let bodyLabel: UITextView = {
|
||||
let label = UITextView()
|
||||
label.isEditable = false
|
||||
label.dataDetectorTypes = [.phoneNumber, .link]
|
||||
label.isScrollEnabled = false
|
||||
label.textContainerInset = .zero
|
||||
label.textContainer.lineFragmentPadding = 0
|
||||
label.font = RobotoFont.regular(with: 14)
|
||||
label.textColor = BKColor.grey.darken4
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
let urlLabel: UILabel = {
|
||||
let label = BKLabel()
|
||||
label.hitTestSlop = UIEdgeInsets(top: -20, left: -20, bottom: -20, right: -20)
|
||||
label.isUserInteractionEnabled = true
|
||||
label.font = RobotoFont.regular(with: 14)
|
||||
label.textColor = BKColor.blue.darken1
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
@ -50,12 +38,6 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
|
||||
return label
|
||||
}()
|
||||
|
||||
let bodyStackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
return stackView
|
||||
}()
|
||||
|
||||
let separatorLine: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.backgroundColor = BKColor.background.primary
|
||||
@ -68,44 +50,55 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
|
||||
|
||||
self.backgroundColor = BKColor.background.primary
|
||||
contentView.addSubview(backgroundPanel)
|
||||
contentView.addSubview(bodyStackView)
|
||||
|
||||
bodyStackView.addArrangedSubview(titleLabel)
|
||||
bodyStackView.addArrangedSubview(bodyLabel)
|
||||
bodyStackView.addArrangedSubview(urlLabel)
|
||||
bodyStackView.spacing = 6
|
||||
bodyStackView.setCustomSpacing(12, after: bodyLabel)
|
||||
contentView.addSubview(bodyLabel)
|
||||
contentView.addSubview(dateLabel)
|
||||
contentView.addSubview(separatorLine)
|
||||
|
||||
self.urlLabel.addGestureRecognizer(UITapGestureRecognizer())
|
||||
|
||||
layoutView()
|
||||
}
|
||||
|
||||
layoutView()
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tap))
|
||||
tap.name = "messageTap"
|
||||
tap.delegate = self
|
||||
bodyLabel.addGestureRecognizer(tap)
|
||||
}
|
||||
|
||||
@objc func tap() {
|
||||
var view = self.superview
|
||||
while view != nil, (view as? UITableView) == nil {
|
||||
view = view?.superview
|
||||
}
|
||||
guard let tableView = view as? UITableView else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let indexPath = tableView.indexPath(for: self) else {
|
||||
return
|
||||
}
|
||||
tableView.delegate?.tableView?(tableView, didSelectRowAt: indexPath)
|
||||
}
|
||||
// 单击手势如果没点击链接,则传递给UITableView didSelectRow
|
||||
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if gestureRecognizer.name == "messageTap", otherGestureRecognizer.name == "UITextInteractionNameLinkTap" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func layoutView() {
|
||||
bodyStackView.snp.makeConstraints { make in
|
||||
make.left.top.equalToSuperview().offset(16)
|
||||
make.right.equalToSuperview().offset(-16)
|
||||
}
|
||||
titleLabel.snp.remakeConstraints { make in
|
||||
make.left.equalTo(12)
|
||||
make.right.equalTo(-12)
|
||||
}
|
||||
|
||||
bodyLabel.snp.remakeConstraints { make in
|
||||
make.left.right.equalTo(titleLabel)
|
||||
}
|
||||
urlLabel.snp.makeConstraints { make in
|
||||
make.left.right.equalTo(bodyLabel)
|
||||
make.top.equalTo(16)
|
||||
make.left.equalTo(28)
|
||||
make.right.equalTo(-28)
|
||||
}
|
||||
dateLabel.snp.remakeConstraints { make in
|
||||
make.left.equalTo(bodyLabel)
|
||||
make.top.equalTo(bodyStackView.snp.bottom).offset(12)
|
||||
make.top.equalTo(bodyLabel.snp.bottom).offset(12)
|
||||
}
|
||||
separatorLine.snp.remakeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
@ -124,16 +117,42 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
|
||||
override func bindViewModel(model: MessageTableViewCellViewModel) {
|
||||
super.bindViewModel(model: model)
|
||||
|
||||
model.title.bind(to: self.titleLabel.rx.text).disposed(by: rx.reuseBag)
|
||||
model.body.bind(to: self.bodyLabel.rx.text).disposed(by: rx.reuseBag)
|
||||
model.url.bind(to: self.urlLabel.rx.text).disposed(by: rx.reuseBag)
|
||||
Observable.combineLatest(model.title, model.body, model.url).subscribe { title, body, url in
|
||||
|
||||
let text = NSMutableAttributedString(
|
||||
string: body,
|
||||
attributes: [.font: RobotoFont.regular(with: 14), .foregroundColor: BKColor.grey.darken4]
|
||||
)
|
||||
|
||||
if title.count > 0 {
|
||||
// 插入一行空行当 spacer
|
||||
text.insert(NSAttributedString(
|
||||
string: "\n",
|
||||
attributes: [.font: RobotoFont.medium(with: 6)]
|
||||
), at: 0)
|
||||
|
||||
text.insert(NSAttributedString(
|
||||
string: title + "\n",
|
||||
attributes: [.font: RobotoFont.medium(with: 16), .foregroundColor: BKColor.grey.darken4]
|
||||
), at: 0)
|
||||
}
|
||||
|
||||
if url.count > 0 {
|
||||
// 插入一行空行当 spacer
|
||||
text.append(NSAttributedString(
|
||||
string: "\n ",
|
||||
attributes: [.font: RobotoFont.medium(with: 8)]
|
||||
))
|
||||
|
||||
text.append(NSAttributedString(string: "\n\(url)", attributes: [
|
||||
.font: RobotoFont.regular(with: 14),
|
||||
.foregroundColor: BKColor.grey.darken4,
|
||||
.link: url
|
||||
]))
|
||||
}
|
||||
|
||||
self.bodyLabel.attributedText = text
|
||||
}.disposed(by: rx.disposeBag)
|
||||
model.date.bind(to: self.dateLabel.rx.text).disposed(by: rx.reuseBag)
|
||||
|
||||
model.title.map { $0.count <= 0 }.bind(to: self.titleLabel.rx.isHidden).disposed(by: rx.reuseBag)
|
||||
model.url.map { $0.count <= 0 }.bind(to: self.urlLabel.rx.isHidden).disposed(by: rx.reuseBag)
|
||||
|
||||
self.urlLabel.gestureRecognizers?.first?.rx.event
|
||||
.map { [weak self] _ in self?.urlLabel.text ?? "" }
|
||||
.bind(to: model.urlTap).disposed(by: rx.reuseBag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ class MessageTableViewCellViewModel: ViewModel {
|
||||
let url: BehaviorRelay<String>
|
||||
let date: BehaviorRelay<String>
|
||||
|
||||
let urlTap: PublishRelay<String>
|
||||
|
||||
init(message: Message) {
|
||||
self.message = message
|
||||
@ -28,8 +27,7 @@ class MessageTableViewCellViewModel: ViewModel {
|
||||
self.body = BehaviorRelay<String>(value: message.body ?? "")
|
||||
self.url = BehaviorRelay<String>(value: message.url ?? "")
|
||||
self.date = BehaviorRelay<String>(value: (message.createDate ?? Date()).agoFormatString())
|
||||
|
||||
self.urlTap = PublishRelay<String>()
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ class iCloudStatusCell: UITableViewCell {
|
||||
self.backgroundColor = BKColor.background.secondary
|
||||
self.textLabel?.text = NSLocalizedString("iCloudSatatus")
|
||||
self.detailTextLabel?.text = ""
|
||||
self.detailTextLabel?.textColor = BKColor.grey.darken2
|
||||
CKContainer.default().accountStatus { status, _ in
|
||||
dispatch_sync_safely_main_queue {
|
||||
switch status {
|
||||
|
||||
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@ -0,0 +1 @@
|
||||
bark.day.app
|
||||
34
docs/README.md
Normal file
34
docs/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
### Bark <!-- {docsify-ignore-all} -->
|
||||
- 免费、轻量!简单调用接口即可给自己的iPhone发送推送。
|
||||
- 依赖苹果APNs,及时、稳定、可靠
|
||||
- 不会消耗设备的电量, 基于系统推送服务与推送扩展,APP本体并不需要运行。
|
||||
- 隐私安全,可以通过一些方式确保包含作者本人在内的所有人都无法窃取你的隐私。<br>*点击详细了解如何保障[隐私安全](/privacy)*
|
||||
|
||||
### 源码
|
||||
- [Bark](https://github.com/Finb/Bark) 是完整开源的 iOS APP,用来接收自定义推送。
|
||||
- [bark-server](https://github.com/Finb/bark-server) 是完整开源的 Bark 服务后端,用来接收用户的推送请求并转发给苹果APNS。
|
||||
|
||||
### 反馈
|
||||
- [Bark 问题反馈群](https://t.me/joinchat/OsCbLzovUAE0YjY1) (注意点击入群验证)
|
||||
- [GitHub Issues](https://github.com/Finb/Bark/issues)
|
||||
|
||||
### 免费
|
||||
Bark **2018年7月**上线,至少会维持运营到 **2031年7月** 。*(说不出口“永久”这个词,后续还有需求再续吧)*<br>
|
||||
APP在维持期间,不会有任何形式的收费与广告,各位彦祖放心使用。
|
||||
|
||||
### 赞助
|
||||
目前仅接收 GitHub 赞助,同时非常感谢每一位赞助者<br>
|
||||
赞助者:[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
|
||||
|
||||
### 文档
|
||||
- **App**
|
||||
- [使用教程](/tutorial)
|
||||
- [推送加密](/encryption)
|
||||
- [常见问题](/faq)
|
||||
- **服务端**
|
||||
- [部署服务](/deploy)
|
||||
- [直接推送](/apns)
|
||||
- [编译代码](/build)
|
||||
- [推送证书](/cert)
|
||||
- [隐私安全](/privacy)
|
||||
11
docs/_coverpage.md
Executable file
11
docs/_coverpage.md
Executable file
@ -0,0 +1,11 @@
|
||||

|
||||
|
||||
# Bark <small></small>
|
||||
|
||||
> 一款注重隐私、安全可控的自定义通知推送工具。
|
||||
|
||||
- 免费、简单、安全
|
||||
- 打开即用
|
||||
|
||||
[GitHub](https://github.com/finb/bark)
|
||||
[Get Started](#bark)
|
||||
BIN
docs/_media/Icon.png
Normal file
BIN
docs/_media/Icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
BIN
docs/_media/environment.png
Normal file
BIN
docs/_media/environment.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
BIN
docs/_media/example.jpg
Normal file
BIN
docs/_media/example.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
5
docs/_navbar.md
Normal file
5
docs/_navbar.md
Normal file
@ -0,0 +1,5 @@
|
||||
* Translations
|
||||
- [:cn: 简体中文](/)
|
||||
- [:uk: English](/en-us/)
|
||||
- [:tr: Türkçe](/tr/)
|
||||
|
||||
11
docs/_sidebar.md
Normal file
11
docs/_sidebar.md
Normal file
@ -0,0 +1,11 @@
|
||||
- [Bark](/#bark)
|
||||
- **App**
|
||||
- [使用教程](/tutorial)
|
||||
- [推送加密](/encryption)
|
||||
- [常见问题](/faq)
|
||||
- **服务端**
|
||||
- [部署服务](/deploy)
|
||||
- [直接推送](/apns)
|
||||
- [编译代码](/build)
|
||||
- [推送证书](/cert)
|
||||
- [隐私安全](/privacy)
|
||||
55
docs/apns.md
Normal file
55
docs/apns.md
Normal file
@ -0,0 +1,55 @@
|
||||
### 直接调用APNS接口
|
||||
如果有设备的 DeviceToken(可在APP中查看),就可以调用苹果APNS接口直接给设备发推送,APP中也无需添加服务器。<br>
|
||||
以下是命令行发推送示例:
|
||||
|
||||
```shell
|
||||
# 设置环境变量
|
||||
# 下载 key https://raw.githubusercontent.com/Finb/bark-server/master/deploy/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8
|
||||
# 将 key 文件路径填到下面
|
||||
TOKEN_KEY_FILE_NAME=
|
||||
# 从 app 设置中复制 DeviceToken 到这
|
||||
DEVICE_TOKEN=
|
||||
|
||||
#下面的不要修改
|
||||
TEAM_ID=5U8LBRXG3A
|
||||
AUTH_KEY_ID=LH4T9V5U4R
|
||||
TOPIC=me.fin.bark
|
||||
APNS_HOST_NAME=api.push.apple.com
|
||||
|
||||
# 生成TOKEN
|
||||
JWT_ISSUE_TIME=$(date +%s)
|
||||
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
|
||||
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
|
||||
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
|
||||
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
|
||||
# 如果有条件,最好改进脚本缓存此 Token。Token 30分钟内复用同一个,每过30分钟重新生成
|
||||
# 苹果文档指明 TOKEN 生成间隔最短20分钟,TOKEN 有效期最长60分钟
|
||||
# 间隔过短重复生成会生成失败,TOKEN 超过1小时不重新生成就不能推送
|
||||
# 但经我不负责任的简单测试可以短时间内正常生成
|
||||
# 此处仅提醒,或许可能因频繁生成 TOKEN 导致推送失败
|
||||
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
|
||||
|
||||
#发送推送
|
||||
curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"test"}}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
|
||||
|
||||
```
|
||||
|
||||
### 推送参数格式
|
||||
参考 https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification<br>
|
||||
一定要带上 "mutable-content" : 1 ,否则推送扩展不执行,不会保存推送。<br>
|
||||
|
||||
示例:
|
||||
```js
|
||||
{
|
||||
"aps": {
|
||||
"mutable-content": 1,
|
||||
"alert": {
|
||||
"title" : "title",
|
||||
"body": "body"
|
||||
},
|
||||
"category": "myNotificationCategory",
|
||||
"sound": "minuet.caf"
|
||||
},
|
||||
"icon": "https://day.app/assets/images/avatar.jpg"
|
||||
}
|
||||
```
|
||||
47
docs/build.md
Normal file
47
docs/build.md
Normal file
@ -0,0 +1,47 @@
|
||||
## 下载源码
|
||||
从GitHub下载源码 [bark-server](https://github.com/Finb/bark-server)
|
||||
|
||||
或
|
||||
```sh
|
||||
git clone https://github.com/Finb/bark-server.git
|
||||
```
|
||||
## 配置依赖
|
||||
- Golang 1.18+
|
||||
- Go Mod (env GO111MODULE=on)
|
||||
- Go Mod Proxy (env GOPROXY=https://goproxy.cn)
|
||||
- 安装 [go-task](https://taskfile.dev/installation/)
|
||||
|
||||
## 交叉编译所有平台
|
||||
```sh
|
||||
task
|
||||
```
|
||||
|
||||
## 编译指定平台
|
||||
```sh
|
||||
task linux_amd64
|
||||
task linux_amd64_v3
|
||||
```
|
||||
|
||||
## 支持的平台
|
||||
|
||||
- linux_386
|
||||
- linux_amd64
|
||||
- linux_amd64_v2
|
||||
- linux_amd64_v3
|
||||
- linux_amd64_v4
|
||||
- linux_armv5
|
||||
- linux_armv6
|
||||
- linux_armv7
|
||||
- linux_armv8
|
||||
- linux_mips_hardfloat
|
||||
- linux_mipsle_softfloat
|
||||
- linux_mipsle_hardfloat
|
||||
- linux_mips64
|
||||
- linux_mips64le
|
||||
- windows_386.exe
|
||||
- windows_amd64.exe
|
||||
- windows_amd64_v2.exe
|
||||
- windows_amd64_v3.exe
|
||||
- windows_amd64_v4.exe
|
||||
- darwin_amd64
|
||||
- darwin_arm64
|
||||
6
docs/cert.md
Normal file
6
docs/cert.md
Normal file
@ -0,0 +1,6 @@
|
||||
当你需要集成Bark到自己的系统或重新实现后端代码时可能需要推送证书
|
||||
|
||||
##### 有效期到: *永久*
|
||||
##### Key ID:*LH4T9V5U4R*
|
||||
##### TeamID:*5U8LBRXG3A*
|
||||
##### 下载地址:[AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8](https://github.com/Finb/bark-server/releases/download/v1.0.2/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8)
|
||||
86
docs/deploy.md
Normal file
86
docs/deploy.md
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
## Docker
|
||||
```
|
||||
docker run -dt --name bark -p 8080:8080 -v `pwd`/bark-data:/data finab/bark-server
|
||||
```
|
||||
|
||||
## Docker-Compose
|
||||
```
|
||||
mkdir bark && cd bark
|
||||
curl -sL https://git.io/JvSRl > docker-compose.yaml
|
||||
docker-compose up -d
|
||||
```
|
||||
## 手动部署
|
||||
|
||||
1. 根据平台下载可执行文件:<br> <a href='https://github.com/Finb/bark-server/releases'>https://github.com/Finb/bark-server/releases</a><br>
|
||||
或自己编译<br>
|
||||
<a href="https://github.com/Finb/bark-server">https://github.com/Finb/bark-server</a>
|
||||
|
||||
2. 运行
|
||||
```
|
||||
./bark-server_linux_amd64 -addr 0.0.0.0:8080 -data ./bark-data
|
||||
```
|
||||
3. 你可能需要
|
||||
```
|
||||
chmod +x bark-server_linux_amd64
|
||||
```
|
||||
请注意 bark-server 默认使用 /data 目录保存数据,请确保 bark-server 有权限读写 /data 目录,或者你可以使用 `-data` 选项指定一个目录
|
||||
|
||||
## Serverless
|
||||
|
||||
|
||||
默认提供 Heroku ~~免费~~ 一键部署 (2022-11-28日后收费)<br>
|
||||
[](https://heroku.com/deploy?template=https://github.com/finb/bark-server)<br>
|
||||
|
||||
其他支持WEB路由的 serverless 服务器可以使用 `bark-server -serverless true` 开启。
|
||||
|
||||
开启后, bark-server 会读取系统环境变量 BARK_KEY 和 BARK_DEVICE_TOKEN, 需提前设置好。
|
||||
|
||||
| 变量名 | 填写要求 |
|
||||
| ---- | ---- |
|
||||
| BARK_KEY | 除了不能填 "push" 外,可以随便填写你喜欢的。|
|
||||
| BARK_DEVICE_TOKEN | Bark App 设置中显示的 DeviceToken,此 Token 是 APNS 真实设备 Token ,请不要泄露 |
|
||||
|
||||
请注意 Serverless 模式只允许一台设备使用
|
||||
|
||||
## Render
|
||||
Render 能非常简单的创建免费的 bark-server
|
||||
1. [注册](https://dashboard.render.com/register/)一个 Render 账号
|
||||
2. 创建一个 [New Web Service](https://dashboard.render.com/select-repo?type=web)
|
||||
3. 在底部的 **Public Git repository** 输入框输入下面的URL
|
||||
```
|
||||
https://github.com/Finb/bark-server
|
||||
```
|
||||
4. 点击 **Continue** 输入表单
|
||||
* Name - 名称,随便取个名字,例如 bark-server
|
||||
* Region - 服务器地区,选择离你近的
|
||||
* Start Command - 程序执行命令,填`./app -serverless true`。(注意不要漏了 ./app 前面的点)
|
||||
* Instance Type - 选 Free ,免费的足够用了。
|
||||
* 点击 Advanced 展开更多选项
|
||||
* 点击 Add Environment Variable 添加 Serverless 模式需要的 BARK_KEY 和 BARK_DEVICE_TOKEN 字段。 (填写要求参考 [Serverless](#Serverless)) <br><img src="../_media/environment.png" />
|
||||
* 其他的默认不动
|
||||
5. 点击底部的 Create Web Service 按钮,然后等待状态从 In progress 变成 Live,可能需要几分钟到十几分钟。
|
||||
6. 页面顶部找到你的服务器URL,这个就是bark-server服务器URL,在 Bark App 中添加即可
|
||||
```
|
||||
https://[your-server-name].onrender.com
|
||||
```
|
||||
7. 如果添加失败,可以等待一段时间再试,有可能服务还没准备好。
|
||||
8. 不添加到 Bark App 中也可以,直接调用就能发推送。BARK_KEY 就是上面环境变量中你填写的。
|
||||
```
|
||||
https://[your-server-name].onrender.com/BARK_KEY/推送内容
|
||||
```
|
||||
|
||||
## 测试
|
||||
```
|
||||
curl http://0.0.0.0:8080/ping
|
||||
```
|
||||
返回 pong 就证明部署成功了
|
||||
|
||||
## 其他
|
||||
|
||||
1. APP端负责将<a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application">DeviceToken</a>发送到服务端。 <br>服务端收到一个推送请求后,将发送推送给Apple服务器。然后手机收到推送
|
||||
|
||||
2. 服务端代码: <a href='https://github.com/Finb/bark-server'>https://github.com/Finb/bark-server</a><br>
|
||||
|
||||
3. App代码: <a href="https://github.com/Finb/Bark">https://github.com/Finb/Bark</a>
|
||||
|
||||
34
docs/en-us/README.md
Normal file
34
docs/en-us/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
### Bark <!-- {docsify-ignore-all} -->
|
||||
- Free, lightweight! Simply call the interface to send push notifications to your own iPhone.
|
||||
- Depends on Apple APNs, timely, stable and reliable
|
||||
- Does not consume device’s battery power. Based on system push service and push extension, APP itself does not need to run.
|
||||
- Privacy and security. You can ensure that no one, including the author himself, can steal your privacy through some ways.<br>*Click for details on how to ensure [privacy and security](/en-us/privacy)*
|
||||
|
||||
### Source code
|
||||
- [Bark](https://github.com/Finb/Bark) is a fully open source iOS APP for receiving custom push notifications
|
||||
- [bark-server](https://github.com/Finb/bark-server) is a fully open source Bark service backend for receiving user push requests and forwarding them to Apple APNS.
|
||||
|
||||
### Feedback
|
||||
- [Telegram Group](https://t.me/joinchat/OsCbLzovUAE0YjY1) (Note: click verification to join group)
|
||||
- [GitHub Issues](https://github.com/Finb/Bark/issues)
|
||||
|
||||
### Free
|
||||
Bark was launched in **July 2018** and will maintain operation until at least **July 2031**. *(Can’t say the word “permanent”, let’s renew it later if there is still demand)*<br>
|
||||
APP will not have any form of charge or advertisement during maintenance period. Feel free to use it.
|
||||
|
||||
### Sponsorship
|
||||
Currently only accept GitHub sponsorship. Thank you very much for every sponsor<br>
|
||||
Sponsors:[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
|
||||
|
||||
### Documentation
|
||||
- **App**
|
||||
- [Tutorial](/en-us/tutorial)
|
||||
- [Encryption](/en-us/encryption)
|
||||
- [FAQs](/en-us/faq)
|
||||
- **Server**
|
||||
- [Deploy](/en-us/deploy)
|
||||
- [Direct Push](/en-us/apns)
|
||||
- [Build](/en-us/build)
|
||||
- [Certificate](/en-us/cert)
|
||||
- [Privacy](/en-us/privacy)
|
||||
11
docs/en-us/_coverpage.md
Executable file
11
docs/en-us/_coverpage.md
Executable file
@ -0,0 +1,11 @@
|
||||

|
||||
|
||||
# Bark <small></small>
|
||||
|
||||
> A privacy-focused, secure and controllable custom notification push tool.
|
||||
|
||||
- Free, simple and safe
|
||||
- Ready to use
|
||||
|
||||
[GitHub](https://github.com/finb/bark)
|
||||
[Get Started](#bark)
|
||||
11
docs/en-us/_sidebar.md
Normal file
11
docs/en-us/_sidebar.md
Normal file
@ -0,0 +1,11 @@
|
||||
- [Bark](/en-us/#bark)
|
||||
- **App**
|
||||
- [Tutorial](/en-us/tutorial)
|
||||
- [Encryption](/en-us/encryption)
|
||||
- [FAQs](/en-us/faq)
|
||||
- **Server**
|
||||
- [Deploy](/en-us/deploy)
|
||||
- [Direct Push](/en-us/apns)
|
||||
- [Build](/en-us/build)
|
||||
- [Certificate](/en-us/cert)
|
||||
- [Privacy](/en-us/privacy)
|
||||
1
docs/en-us/apns.md
Normal file
1
docs/en-us/apns.md
Normal file
@ -0,0 +1 @@
|
||||
...
|
||||
1
docs/en-us/build.md
Normal file
1
docs/en-us/build.md
Normal file
@ -0,0 +1 @@
|
||||
...
|
||||
1
docs/en-us/cert.md
Normal file
1
docs/en-us/cert.md
Normal file
@ -0,0 +1 @@
|
||||
...
|
||||
1
docs/en-us/deploy.md
Normal file
1
docs/en-us/deploy.md
Normal file
@ -0,0 +1 @@
|
||||
...
|
||||
36
docs/en-us/encryption.md
Normal file
36
docs/en-us/encryption.md
Normal file
@ -0,0 +1,36 @@
|
||||
#### What is push encryption
|
||||
Push encryption is a method to protect the push content, which uses a custom key to encrypt and decrypt the push content when sending and receiving. In this way, the push content will not be obtained or leaked by Bark server and Apple APNs server during transmission.
|
||||
|
||||
#### Set custom key
|
||||
1. Open APP homepage
|
||||
2. Find “Push Encryption”, click Encryption Settings
|
||||
3. Select encryption algorithm, fill in KEY as required, click Done to save custom key
|
||||
|
||||
#### Send encrypted push
|
||||
To send an encrypted push, you need to first convert the Bark request parameters into a json format string, then use the previously set key and corresponding algorithm to encrypt the string, and finally send the encrypted ciphertext as ciphertext parameter to the server.<br><br>
|
||||
**For example:**
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# bark key
|
||||
deviceKey='F5u42Bd3HyW8KxkUqo2gRA'
|
||||
# push payload
|
||||
json='{"body": "test", "sound": "birdsong"}'
|
||||
|
||||
# must be 16 bit long
|
||||
key='1234567890123456'
|
||||
iv='1111111111111111'
|
||||
|
||||
# openssl requires Hex encoding of manual keys and IVs, not ASCII encoding.
|
||||
key=$(printf $key | xxd -ps -c 200)
|
||||
iv=$(printf $iv | xxd -ps -c 200)
|
||||
|
||||
ciphertext=$(echo -n $json | openssl enc -aes-128-cbc -K $key -iv $iv | base64)
|
||||
|
||||
# The console will print "d3QhjQjP5majvNt5CjsvFWwqqj2gKl96RFj5OO+u6ynTt7lkyigDYNA3abnnCLpr"
|
||||
echo $ciphertext
|
||||
|
||||
curl --data-urlencode "ciphertext=$ciphertext" http://api.day.app/$deviceKey
|
||||
```
|
||||
46
docs/en-us/faq.md
Normal file
46
docs/en-us/faq.md
Normal file
@ -0,0 +1,46 @@
|
||||
#### Push usage limit <!-- {docsify-ignore-all} -->
|
||||
Normal requests (HTTP status code 200) have no limit.<br>
|
||||
But if more than 1000 error requests (HTTP status code 400 404 500) are made within 5 minutes, <b>the IP will be BAN for 24 hours</b>
|
||||
|
||||
#### Time-sensitive notifications not work
|
||||
You can try to <b>restart your device</b> to solve it.
|
||||
|
||||
#### Unable to save notification history, or unable to copy by pulling down push or swiping left on lock screen without clicking copy button
|
||||
You can try to <b>restart your device</b> to solve it.<br />
|
||||
Due to some reasons, the push service extension ([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension)) failed to run normally, and the code for saving notifications was not executed properly.
|
||||
|
||||
#### Automatic copy push failure
|
||||
After iOS 14.5 version, due to permission tightening, it is not possible to automatically copy push content to clipboard when receiving push. <br/>
|
||||
You can temporarily pull down push or swipe left on lock screen and click view to automatically copy, or click copy button on pop-up push.
|
||||
|
||||
#### Open notification history page by default
|
||||
When you open APP again, it will jump to the last opened page.<br />
|
||||
Just exit APP when you are on history message page. When you open APP again, it will be history message page.
|
||||
|
||||
#### Does push API support POST request?
|
||||
Bark supports GET POST , supports using Json <br>
|
||||
No matter which request method, parameter names are the same. Refer to [usage tutorial](/tutorial)
|
||||
|
||||
#### Pushing special characters causes push failure. For example: Push content contains link or Push abnormal such as + becomes space
|
||||
This is because of the problem of irregular link. It often happens<br>
|
||||
When splicing URL, pay attention to URL encoding parameters
|
||||
|
||||
```sh
|
||||
# For example
|
||||
https://api.day.app/key/{push content}
|
||||
|
||||
# If {push content} is
|
||||
"a/b/c/"
|
||||
|
||||
# Then the final spliced URL is
|
||||
https://api.day.app/key/a/b/c/
|
||||
# The corresponding route will not be found and the backend program will return 404
|
||||
|
||||
#You should url encode {push content} before splicing
|
||||
https://api.day.app/key/a%2Fb%2Fc%2F
|
||||
```
|
||||
If you use a mature HTTP library, parameters will be automatically processed and you don’t need manual encoding. <br>
|
||||
But if you splice URL yourself, you need special attention for special characters in parameters. **It’s better not care whether there are special characters or not and blindly apply a layer of URL encoding.**
|
||||
|
||||
#### How to ensure privacy and security
|
||||
Refer [privacy security](/en-us/privacy)
|
||||
1
docs/en-us/privacy.md
Normal file
1
docs/en-us/privacy.md
Normal file
@ -0,0 +1 @@
|
||||
...
|
||||
1
docs/en-us/tutorial.md
Normal file
1
docs/en-us/tutorial.md
Normal file
@ -0,0 +1 @@
|
||||
...
|
||||
37
docs/encryption.md
Normal file
37
docs/encryption.md
Normal file
@ -0,0 +1,37 @@
|
||||
#### 什么是推送加密
|
||||
推送加密是一种保护推送内容的方法,它使用自定义秘钥在发送和接收时对推送内容进行加密和解密。<br>这样,推送内容在传输过程中就不会被 Bark 服务器和苹果 APNs 服务器获取或泄露。
|
||||
|
||||
#### 设置自定义秘钥
|
||||
1. 打开APP首页
|
||||
2. 找到 “推送加密” ,点击加密设置
|
||||
3. 选择加密算法,按要求填写KEY,点击完成保存自定义秘钥
|
||||
|
||||
#### 发送加密推送
|
||||
要发送加密推送,首先需要把 Bark 请求参数转换成 json 格式的字符串,然后用之前设置的秘钥和相应的算法对字符串进行加密,最后把加密后的密文作为ciphertext参数发送到服务器。<br><br>
|
||||
**示例:**
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# bark key
|
||||
deviceKey='F5u42Bd3HyW8KxkUqo2gRA'
|
||||
# push payload
|
||||
json='{"body": "test", "sound": "birdsong"}'
|
||||
|
||||
# must be 16 bit long
|
||||
key='1234567890123456'
|
||||
iv='1111111111111111'
|
||||
|
||||
# openssl requires Hex encoding of manual keys and IVs, not ASCII encoding.
|
||||
key=$(printf $key | xxd -ps -c 200)
|
||||
iv=$(printf $iv | xxd -ps -c 200)
|
||||
|
||||
ciphertext=$(echo -n $json | openssl enc -aes-128-cbc -K $key -iv $iv | base64)
|
||||
|
||||
# The console will print "d3QhjQjP5majvNt5CjsvFWwqqj2gKl96RFj5OO+u6ynTt7lkyigDYNA3abnnCLpr"
|
||||
echo $ciphertext
|
||||
|
||||
# URL encoding the ciphertext, there may be special characters.
|
||||
curl --data-urlencode "ciphertext=$ciphertext" http://api.day.app/$deviceKey
|
||||
```
|
||||
51
docs/faq.md
Normal file
51
docs/faq.md
Normal file
@ -0,0 +1,51 @@
|
||||
#### 江苏移动用户提示“服务器错误,不能使用推送服务” <!-- {docsify-ignore-all} -->
|
||||
江苏移动存在DNS解析污染,可以更换DNS、翻墙、换网络或者更换 bark 服务器域名 https://api1.day.app ,<br>
|
||||
api1 与 api 除了域名不一样其他完全一样,是同一台服务器可以无痛切换<br>
|
||||
如果已经注册了设备,则只要保证发送端能正常访问 api.day.app 即可,APP无需连接服务器也能正常接收推送。
|
||||
|
||||
#### 推送使用次数限制
|
||||
正常请求(HTTP状态码为200)无任何限制。<br>
|
||||
但如果在5分钟内超过1000次错误请求(HTTP状态码为400 404 500)<b>IP会被 BAN 24小时</b>
|
||||
|
||||
#### 时效性通知无效
|
||||
可以尝试<b>重启设备</b>来解决。
|
||||
|
||||
#### 无法保存通知历史,或下拉推送没有点击复制按钮无法复制
|
||||
可以尝试<b>重启设备</b>来解决。<br />
|
||||
因某些原因导致推送服务扩展([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension))未能正常运行,执行通知保存的代码未能正常执行。
|
||||
|
||||
#### 自动复制推送失效
|
||||
iOS 14.5 之后的版本因权限收紧,不能在收到推送时自动复制推送内容到剪切板。<br/>
|
||||
可暂时先下拉推送或在锁屏界面左滑推送点查看即可自动复制,或点击弹出的推送复制按钮。
|
||||
|
||||
#### 默认打开通知历史列表
|
||||
再次开启APP时,会跳转到上次打开的页面。<br />
|
||||
只需退出APP时,停留在历史消息页面,再次打开APP时就是历史消息页面。
|
||||
|
||||
#### 推送 API 是否支持 POST 请求?
|
||||
Bark支持 GET POST ,支持使用Json<br>
|
||||
无论哪种请求方式,参数名都一样, 参考[使用教程](/tutorial#请求方式)
|
||||
|
||||
#### 推送特殊字符导致推送失败,比如 推送内容包含链接,或推送异常 比如 + 变成空格
|
||||
这是因为整个链接不规范导致的问题,常发生在自己手动拼接URL时。<br>
|
||||
拼接URL时,注意将参数进行URL编码
|
||||
|
||||
```sh
|
||||
# 例如
|
||||
https://api.day.app/key/{推送内容}
|
||||
|
||||
# 如果{推送内容}是
|
||||
"a/b/c/"
|
||||
|
||||
# 则最后拼接的URL是
|
||||
https://api.day.app/key/a/b/c/
|
||||
# 将找不到对应的路由,后端程序将返回404
|
||||
|
||||
# 应该将 {推送内容} url编码后再进行拼接
|
||||
https://api.day.app/key/a%2Fb%2Fc%2F
|
||||
```
|
||||
如果是使用成熟的HTTP库时,参数都会被自动处理,无需自己手动编码。<br>
|
||||
但如果是自己去拼接URL时,则需要特别注意参数中的特殊字符,**最好不管有没有特殊字符,无脑套一层URL编码**。
|
||||
|
||||
#### 如何保障隐私安全
|
||||
参考[隐私安全](/privacy)
|
||||
34
docs/index.html
Normal file
34
docs/index.html
Normal file
@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Bark</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="Description">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'Bark',
|
||||
nameLink: '/#/?id=源码',
|
||||
// repo: 'https://github.com/Finb/Bark',
|
||||
logo: '/_media/Icon.png height=60px',
|
||||
coverpage: true,
|
||||
loadSidebar: true,
|
||||
subMaxLevel: 4,
|
||||
loadNavbar: true,
|
||||
auto2top: true,
|
||||
coverpage: ['/', '/en-us/', '/tr/'],
|
||||
}
|
||||
</script>
|
||||
<!-- Docsify v4 -->
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
31
docs/privacy.md
Normal file
31
docs/privacy.md
Normal file
@ -0,0 +1,31 @@
|
||||
#### 隐私如何泄露 <!-- {docsify-ignore-all} -->
|
||||
一条推送从发送到接收经过路线是:<br>
|
||||
发送端 <font color='red'> →服务端①</font> → 苹果APNS服务器 → 你的设备 → <font color='red'>Bark APP②</font>。
|
||||
|
||||
红色的两处地方可能泄露隐私 <br>
|
||||
* 发送端未使用HTTPS或使用公共服务器*(作者会看到请求日志)*
|
||||
* Bark App 本身不安全,上传到 App Store 的版本经过修改。
|
||||
|
||||
#### 解决服务端隐私问题
|
||||
* 你可以使用开源的后端代码,自行[部署后端服务](/deploy.md),开启HTTPS。
|
||||
* 使用自定义秘钥的[加密推送](/encryption) ,加密推送内容
|
||||
|
||||
#### 保证 APP 完全由开源代码构建
|
||||
为确保 App 是安全、未经任何人(包含作者)修改过的,Bark 是由 GitHub Actions 构建后上传到 App Store。<br>
|
||||
Bark应用设置内可以查看到 GitHub Run Id,点击可在里面找到当前版本构建所使用的配置文件、编译时的源代码、上传到 App Store 的版本 build 号 等等信息。<br>
|
||||
|
||||
|
||||
同一个版本 build 号仅能上传到 App Store 一次,所以这个号是唯一的。<br>
|
||||
可用此号对比从商店下载的 Bark App,如果一致则证明从 App Store 下载的 App 是完全由开源代码构建。
|
||||
|
||||
举例: Bark 1.2.9 - 3 <br>
|
||||
https://github.com/Finb/Bark/actions/runs/3327969456
|
||||
|
||||
1. 找到编译时的 commit id ,可以查看编译时完整的源码
|
||||
2. 查看 .github/workflows/testflight.yaml ,验证所有 Action ,确保 Action 打印的日志未被篡改
|
||||
3. 查看 Action Logs https://github.com/Finb/Bark/actions/runs/3327969456/jobs/5503414528
|
||||
4. 找到 打包的App ID、Team ID、上传到 App Store 的版本与 build 号等信息。
|
||||
5. 下载商店对应版本ipa,比对版本build号是否与日志中一致*(这个号码同一个APP是唯一的,成功上传了就不能再以相同的版本build号上传)*
|
||||
|
||||
|
||||
*这里不考虑iOS是否泄露隐私*
|
||||
34
docs/tr/README.md
Normal file
34
docs/tr/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
### Bark <!-- {docsify-ignore-all} -->
|
||||
- Ücretsiz, hafif! Kendi iPhone'unuza anlık bildirimler göndermek için arayüzü çağırmanız yeterlidir.
|
||||
- Apple APN'lerine bağlıdır, zamanında, istikrarlı ve güvenilirdir
|
||||
- Cihazın pil gücünü tüketmez. Sistem anlık bildirim hizmeti ve anlık bildirim uzantısına bağlı olarak, APP'nin kendisinin çalışması gerekmez.
|
||||
- Gizlilik ve güvenlik. Yazarın kendisi de dahil olmak üzere hiç kimsenin bazı yollarla gizliliğinizi çalamayacağından emin olabilirsiniz.<br>*[Gizlilik ve güvenliğin](/tr/privacy) nasıl sağlanacağına ilişkin ayrıntılar için tıklayın*
|
||||
|
||||
### Kaynak Kodu
|
||||
- [Bark](https://github.com/Finb/Bark) özel anlık bildirimler almak için tamamen açık kaynaklı bir iOS uygulamasıdır
|
||||
- [bark-server](https://github.com/Finb/bark-server) kullanıcı anlık bildirim isteklerini almak ve bunları Apple APNS'ye iletmek için tamamen açık kaynaklı bir Bark hizmeti arka ucudur.
|
||||
|
||||
### Geri Bildirim
|
||||
- [Telegram Grubu](https://t.me/joinchat/OsCbLzovUAE0YjY1) (Not: gruba katılmak için doğrulamaya tıklayın)
|
||||
- [GitHub Sorunlar](https://github.com/Finb/Bark/issues)
|
||||
|
||||
### Ücretsiz
|
||||
Bark **Temmuz 2018**'de başlatıldı ve en az **Temmuz 2031**'e kadar işletilmeye devam edecek. *("Kalıcı" kelimesini söyleyemem, hala talep varsa daha sonra yenileyelim.)*<br>
|
||||
Uygulama, bakım süresi boyunca herhangi bir şekilde ücretlendirilmeyecek veya reklam içermeyecektir. Kullanmaktan çekinmeyin.
|
||||
|
||||
### Sponsorluk
|
||||
Şu anda yalnızca GitHub sponsorluğunu kabul ediyoruz. Her sponsor için çok teşekkür ederim<br>
|
||||
Sponsorlar:[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
|
||||
|
||||
### Dokümantasyon
|
||||
- **Uygulama**
|
||||
- [Öğretici](/tr/tutorial)
|
||||
- [Şifreleme](/tr/encryption)
|
||||
- [SSS](/tr/faq)
|
||||
- **Sunucu**
|
||||
- [Dağıtım](/tr/deploy)
|
||||
- [Doğrudan Gönderim](/tr/apns)
|
||||
- [Oluşturmak](/tr/build)
|
||||
- [Sertifika](/tr/cert)
|
||||
- [Gizlilik](/tr/privacy)
|
||||
11
docs/tr/_coverpage.md
Executable file
11
docs/tr/_coverpage.md
Executable file
@ -0,0 +1,11 @@
|
||||

|
||||
|
||||
# Bark <small></small>
|
||||
|
||||
> Gizlilik odaklı, güvenli ve kontrol edilebilir bir özel anlık bildirim aracı.
|
||||
|
||||
- Ücretsiz, basit ve güvenli
|
||||
- Kullanıma hazır
|
||||
|
||||
[GitHub](https://github.com/finb/bark)
|
||||
[Başlamak](#bark)
|
||||
11
docs/tr/_sidebar.md
Normal file
11
docs/tr/_sidebar.md
Normal file
@ -0,0 +1,11 @@
|
||||
- [Bark](/tr/#bark)
|
||||
- **Uygulama**
|
||||
- [Öğretici](/tr/tutorial)
|
||||
- [Şifreleme](/tr/encryption)
|
||||
- [SSS](/tr/faq)
|
||||
- **Sunucu**
|
||||
- [Dağıtım](/tr/deploy)
|
||||
- [Doğrudan Gönderim](/tr/apns)
|
||||
- [Oluşturmak](/tr/build)
|
||||
- [Sertifika](/tr/cert)
|
||||
- [Gizlilik](/tr/privacy)
|
||||
55
docs/tr/apns.md
Normal file
55
docs/tr/apns.md
Normal file
@ -0,0 +1,55 @@
|
||||
### APNS Arayüzünü Doğrudan Kullanma
|
||||
Eğer cihazınızın DeviceToken'ı varsa (Uygulama içinde bulunabilir), doğrudan Apple APNS arayüzünü çağırarak cihaza anlık bildirim gönderebilirsiniz. Ayrıca, Uygulamaya sunucu eklemeye gerek yoktur.<br>
|
||||
Aşağıda komut satırında anlık bildirim gönderme örneği verilmiştir:
|
||||
|
||||
```shell
|
||||
# Çevresel değişkenleri ayarla
|
||||
# Key'i indir https://raw.githubusercontent.com/Finb/bark-server/master/deploy/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8
|
||||
# Key dosyasının yolunu aşağıya gir
|
||||
TOKEN_KEY_FILE_NAME=
|
||||
# Uygulama ayarlarından kopyalanan DeviceToken'ı buraya yapıştır
|
||||
DEVICE_TOKEN=
|
||||
|
||||
# Aşağıdakileri değiştirmeyin !!!
|
||||
TEAM_ID=5U8LBRXG3A
|
||||
AUTH_KEY_ID=LH4T9V5U4R
|
||||
TOPIC=me.fin.bark
|
||||
APNS_HOST_NAME=api.push.apple.com
|
||||
|
||||
# TOKEN oluştur
|
||||
JWT_ISSUE_TIME=$(date +%s)
|
||||
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
|
||||
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
|
||||
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
|
||||
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
|
||||
# Eğer koşullar elverişliyse, en iyi performans için bu betiği geliştirerek TOKEN'ı önbelleğe almanız iyi olur.
|
||||
# Aynı TOKEN'i 30 dakika içinde tekrar kullanmak, her 30 dakikada bir yeniden oluşturmaktan daha iyidir.
|
||||
# Apple dokümantasyonu, TOKEN'ın en erken 20 dakika arayla yeniden oluşturulabileceğini belirtmektedir. TOKEN'ın maksimum geçerlilik süresi 60 dakikadır.
|
||||
# Ancak, sık sık yeniden oluşturmak TOKEN'ın başarısız olmasına neden olabilir.
|
||||
# Bu bilgiyi paylaşmak istiyoruz, belki sık sık TOKEN oluşturmak nedeniyle anlık bildirim gönderiminde sorun yaşanabilir.
|
||||
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
|
||||
|
||||
# Anlık bildirim gönderme
|
||||
curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"test"}}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
|
||||
|
||||
```
|
||||
|
||||
### Push Parametre Formatı
|
||||
Bknz. https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification<br>
|
||||
"mutable-content" : 1 getirdiğinizden emin olun, aksi takdirde anlık bildirim uzantısı çalışmayacak ve anlık bildirimi kaydetmeyecektir.
|
||||
|
||||
Örnek:
|
||||
```js
|
||||
{
|
||||
"aps": {
|
||||
"mutable-content": 1,
|
||||
"alert": {
|
||||
"title" : "Başlık",
|
||||
"body": "İçerik"
|
||||
},
|
||||
"category": "bildirimGrubu",
|
||||
"sound": "minuet.caf"
|
||||
},
|
||||
"icon": "https://day.app/assets/images/avatar.jpg"
|
||||
}
|
||||
```
|
||||
47
docs/tr/build.md
Normal file
47
docs/tr/build.md
Normal file
@ -0,0 +1,47 @@
|
||||
## Kaynak Kodu İndirme
|
||||
GitHub'dan kaynak kodunu indirin: [bark-server](https://github.com/Finb/bark-server)
|
||||
|
||||
veya
|
||||
```sh
|
||||
git clone https://github.com/Finb/bark-server.git
|
||||
```
|
||||
## Gerekli Bağımlılıkları Yapılandırma
|
||||
- Golang 1.18+
|
||||
- Go Mod (env GO111MODULE=on)
|
||||
- Go Mod Proxy (env GOPROXY=https://goproxy.cn)
|
||||
- [go-task](https://taskfile.dev/installation/) kurulu olmalı.
|
||||
|
||||
## Tüm Platformlar İçin Çapraz Derleme
|
||||
```sh
|
||||
task
|
||||
```
|
||||
|
||||
## Belirli Bir Platform İçin Derleme
|
||||
```sh
|
||||
task linux_amd64
|
||||
task linux_amd64_v3
|
||||
```
|
||||
|
||||
## Desteklenen Platformlar
|
||||
|
||||
- linux_386
|
||||
- linux_amd64
|
||||
- linux_amd64_v2
|
||||
- linux_amd64_v3
|
||||
- linux_amd64_v4
|
||||
- linux_armv5
|
||||
- linux_armv6
|
||||
- linux_armv7
|
||||
- linux_armv8
|
||||
- linux_mips_hardfloat
|
||||
- linux_mipsle_softfloat
|
||||
- linux_mipsle_hardfloat
|
||||
- linux_mips64
|
||||
- linux_mips64le
|
||||
- windows_386.exe
|
||||
- windows_amd64.exe
|
||||
- windows_amd64_v2.exe
|
||||
- windows_amd64_v3.exe
|
||||
- windows_amd64_v4.exe
|
||||
- darwin_amd64
|
||||
- darwin_arm64
|
||||
6
docs/tr/cert.md
Normal file
6
docs/tr/cert.md
Normal file
@ -0,0 +1,6 @@
|
||||
Bark'ı kendi sistemine entegre etmeniz veya arka uç kodunu yeniden uygulamanız gerektiğinde, anlık bildirim sertifikasına ihtiyacınız olabilir.
|
||||
|
||||
##### Geçerlilik Süresi: *Süresiz*
|
||||
##### Anahtar Kimliği (Key ID):*LH4T9V5U4R*
|
||||
##### Ekip Kimliği (TeamID):*5U8LBRXG3A*
|
||||
##### İndirme Bağlantısı:[AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8](https://github.com/Finb/bark-server/releases/download/v1.0.2/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8)
|
||||
86
docs/tr/deploy.md
Normal file
86
docs/tr/deploy.md
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
## Docker
|
||||
```
|
||||
docker run -dt --name bark -p 8080:8080 -v `pwd`/bark-data:/data finab/bark-server
|
||||
```
|
||||
|
||||
## Docker-Compose
|
||||
```
|
||||
mkdir bark && cd bark
|
||||
curl -sL https://git.io/JvSRl > docker-compose.yaml
|
||||
docker-compose up -d
|
||||
```
|
||||
## Manuel Dağıtım
|
||||
|
||||
1. Platforma göre çalıştırılabilir dosyayı indirin.<br> <a href='https://github.com/Finb/bark-server/releases'>https://github.com/Finb/bark-server/releases</a><br>
|
||||
veya kendiniz derleyin<br>
|
||||
<a href="https://github.com/Finb/bark-server">https://github.com/Finb/bark-server</a>
|
||||
|
||||
2. Çalıştırın
|
||||
```
|
||||
./bark-server_linux_amd64 -addr 0.0.0.0:8080 -data ./bark-data
|
||||
```
|
||||
3. Gerekebilir
|
||||
```
|
||||
chmod +x bark-server_linux_amd64
|
||||
```
|
||||
Lütfen unutmayın ki bark-server varsayılan olarak verileri saklamak için /data dizinini kullanır. Bark-server'ın /data dizinine okuma ve yazma izinlerine sahip olduğundan emin olun veya farklı bir dizini belirtmek için `-data` seçeneğini kullanabilirsiniz.
|
||||
|
||||
## Sunucusuz Mimari (Serverless)
|
||||
|
||||
|
||||
Heroku ücretsiz dağıtım sunar (2022-11-28 tarihine kadar).<br>
|
||||
[](https://heroku.com/deploy?template=https://github.com/finb/bark-server)<br>
|
||||
|
||||
Web yönlendirmelerini destekleyen diğer serverless sunucuları, Serverless modunu etkinleştirmek için `bark-server -serverless true` komutunu kullanabilirsiniz.
|
||||
|
||||
Etkinleştirildiğinde, bark-server sistem ortam değişkenleri BARK_KEY ve BARK_DEVICE_TOKEN'ı okuyacaktır. Bu değerleri önceden ayarlamanız gerekmektedir.
|
||||
|
||||
| Değişken Adı | Gereksinimler |
|
||||
| ---- | ---- |
|
||||
| BARK_KEY | "push" dışında herhangi bir değer girilebilir. |
|
||||
| BARK_DEVICE_TOKEN | Bark App ayarlarında görünen DeviceToken. Bu Token gerçek APNS cihaz tokenidir, lütfen sızdırmayın. |
|
||||
|
||||
Lütfen Serverless modunun yalnızca bir cihaza izin verdiğini unutmayın.
|
||||
|
||||
## Render
|
||||
Render, ücretsiz bir bark-server oluşturmayı çok kolay hale getirir.
|
||||
1. [Render](https://dashboard.render.com/register/) hesabı oluşturun.
|
||||
2. [Yeni Web Hizmeti](https://dashboard.render.com/select-repo?type=web) oluşturun.
|
||||
3. Aşağıdaki URL'yi **Public Git repository** giriş kutusuna girin
|
||||
```
|
||||
https://github.com/Finb/bark-server
|
||||
```
|
||||
4. **Devam et**'i tıklayın ve formu doldurun
|
||||
* **Name** - Ad, herhangi bir ad seçin, örneğin bark-server.
|
||||
* **Region** - Sunucu bölgesi, size en yakın olanı seçin.
|
||||
* **Start Command** - Programı çalıştırma komutu, `./app -serverless true` şeklinde doldurun. (Lütfen ./app öncesindeki noktayı unutmayın)
|
||||
* **Instance Type** - *Free*'yi seçin, ücretsiz olan yeterlidir.
|
||||
* Seçenekleri genişletmek için **Advanced**'e tıklayın.
|
||||
* **Add Environment Variable**'a tıklayarak Serverless modu için gereken BARK_KEY ve BARK_DEVICE_TOKEN alanlarını ekleyin. (Gereksinimler için [Serverless](#Serverless) bölümüne bakın) <br><img src="_media/environment.png" />
|
||||
* Diğer seçenekler değiştirilmez
|
||||
5. Sayfanın alt kısmında **Create Web Service** düğmesine tıklayın ve durumun **In progress**'ten **Live**'a geçmesini bekleyin, bu birkaç dakika ila on dakika sürebilir.
|
||||
6. Sayfanın üst kısmında sunucu URL'nizi bulun, bu bark-server sunucu URL'si, Bark App'e ekleyebilirsiniz
|
||||
```
|
||||
https://[sizin-sunucu-adiniz].onrender.com
|
||||
```
|
||||
7. Sunucu ekleme başarısız olursa bir süre bekleyebilir ve tekrar deneyebilirsiniz. Servis henüz hazır olmayabilir.
|
||||
8. Bark App'e ekleme yapmanıza gerek yoktur, doğrudan çağrı yaparak anlık bildirim gönderebilirsiniz. BARK_KEY yukarıda doldurduğunuz anahtardır.
|
||||
```
|
||||
https://[sizin-sunucu-adiniz].onrender.com/BARK_KEY/推送内容
|
||||
```
|
||||
|
||||
## Test
|
||||
```
|
||||
curl http://0.0.0.0:8080/ping
|
||||
```
|
||||
Eğer **pong** dönerse, dağıtım başarılı demektir.
|
||||
|
||||
## Diğer
|
||||
|
||||
1. Uygulama tarafı sunucu tarafına <a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application">DeviceToken</a>göndermekten sorumludur.<br>Sunucu tarafı bir push isteği aldığında, Apple sunucusuna bir anlık bildirim gönderir. Ardından cep telefonu anlık bildirimi alır.
|
||||
|
||||
2. Sunucu kodu:<a href='https://github.com/Finb/bark-server'>https://github.com/Finb/bark-server</a><br>
|
||||
|
||||
3. Uygulama Kodu: <a href="https://github.com/Finb/Bark">https://github.com/Finb/Bark</a>
|
||||
|
||||
36
docs/tr/encryption.md
Normal file
36
docs/tr/encryption.md
Normal file
@ -0,0 +1,36 @@
|
||||
#### Anlık Bildirim şifreleme nedir?
|
||||
Anlık bildirim şifreleme, Anlık bildirim içeriğini korumak için bir yöntemdir ve gönderme ve alma sırasında Anlık bildirim içeriğini şifrelemek ve şifresini çözmek için özel bir anahtar kullanır. Bu şekilde, Anlık bildirim içeriği iletim sırasında Bark sunucusu ve Apple APNs sunucusu tarafından elde edilemez veya sızdırılamaz.
|
||||
|
||||
#### Özel anahtar ayarlama
|
||||
1. Uygulama ana sayfasını açın.
|
||||
2. "Anlık Bildirim Şifreleme"yi bulun, Şifreleme Ayarları'na tıklayın.
|
||||
3. Şifreleme algoritmasını seçin, ANAHTARI gerektiği gibi doldurun, özel anahtarı kaydetmek için Bitti'ye tıklayın.
|
||||
|
||||
#### Şifreli anlık bildirim gönderme
|
||||
Şifrelenmiş bir anlık bildirim göndermek için önce Bark istek parametrelerini json formatında bir dizeye dönüştürmeniz, ardından dizeyi şifrelemek için önceden ayarlanmış anahtarı ve ilgili algoritmayı kullanmanız ve son olarak şifrelenmiş şifre metnini sunucuya "ciphertext" parametresi olarak göndermeniz gerekir.<br><br>
|
||||
**Örneğin:**
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# bark anahtarı
|
||||
deviceKey='F5u42Bd3HyW8KxkUqo2gRA'
|
||||
# anlık bildirim gönderilen veri
|
||||
json='{"body": "test", "sound": "birdsong"}'
|
||||
|
||||
# 16 bit uzunluğunda olmalıdır
|
||||
key='1234567890123456'
|
||||
iv='1111111111111111'
|
||||
|
||||
# openssl, manuel anahtarların ve IV'lerin ASCII kodlamasını değil, Hex kodlamasını gerektirir.
|
||||
key=$(printf $key | xxd -ps -c 200)
|
||||
iv=$(printf $iv | xxd -ps -c 200)
|
||||
|
||||
ciphertext=$(echo -n $json | openssl enc -aes-128-cbc -K $key -iv $iv | base64)
|
||||
|
||||
# Konsol şunları yazdıracaktır "d3QhjQjP5majvNt5CjsvFWwqqj2gKl96RFj5OO+u6ynTt7lkyigDYNA3abnnCLpr"
|
||||
echo $ciphertext
|
||||
|
||||
curl --data-urlencode "ciphertext=$ciphertext" http://api.day.app/$deviceKey
|
||||
```
|
||||
46
docs/tr/faq.md
Normal file
46
docs/tr/faq.md
Normal file
@ -0,0 +1,46 @@
|
||||
#### Anlık bildirim kullanım limiti <!-- {docsify-ignore-all} -->
|
||||
Normal isteklerin (HTTP durum kodu 200) sınırı yoktur.<br>
|
||||
Ancak 5 dakika içinde 1000'den fazla hata isteği (HTTP durum kodu 400 404 500) yapılırsa, <b>IP 24 saat boyunca YASAKLANACAKTIR.</b>
|
||||
|
||||
#### Zamana duyarlı bildirimler çalışmıyor
|
||||
Sorunu çözmek için <b>cihazınızı yeniden başlatmayı</b> deneyebilirsiniz.
|
||||
|
||||
#### Bildirim geçmişi kaydedilemiyor veya kopyala düğmesine tıklamadan kilit ekranında aşağı iterek veya sola kaydırarak kopyalanamıyor
|
||||
Sorunu çözmek için <b>cihazınızı yeniden başlatmayı</b> deneyebilirsiniz.<br />
|
||||
Bazı nedenlerden dolayı, anlık bildirim hizmeti uzantısı ([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension)) düzgün bir şekilde çalıştırılamadı ve bildirimleri kaydetme kodu düzgün bir şekilde yürütülmedi.
|
||||
|
||||
#### Otomatik kopyalama anlık bildirim hatası
|
||||
iOS 14.5 sürümünden sonra, izinlerin sıkılaştırılması nedeniyle, push bildirimi alırken içeriği otomatik olarak panoya kopyalamak mümkün değildir. <br/>
|
||||
Geçici olarak push bildirimini aşağı çekebilir veya ekran kilidinde sola kaydırarak görüntüleyip otomatik olarak kopyalayabilirsiniz, veya açılır penceredeki kopyala düğmesine tıklayabilirsiniz.
|
||||
|
||||
#### Bildirim geçmişi sayfasını varsayılan olarak aç
|
||||
Uygulamayı tekrar açtığınızda, son açılan sayfaya atlayacaktır.<br />
|
||||
Eğer geçmiş mesaj sayfasındaysanız, uygulamadan çıkın. Uygulamayı tekrar açtığınızda yine geçmiş mesaj sayfasında olacaksınız.
|
||||
|
||||
#### Anlık Bildirim API POST isteğini destekliyor mu?
|
||||
Bark GET POST'u destekler, Json kullanımını destekler.<br>
|
||||
Hangi istek yöntemi olursa olsun, parametre adları aynıdır. Bakınız [kullanım öğreticisi](tr/tutorial)
|
||||
|
||||
#### Özel karakterler anlık bildirim işleminde başarısızlığa neden oluyor. Örneğin: İtme içeriği bağlantı içeriyor veya + gibi özel karakterler boşluğa dönüşüyor.
|
||||
Bunun nedeni düzensiz bağlantı sorunudur. Sıklıkla olur<br>
|
||||
URL eklerken, URL kodlama parametrelerine dikkat edin
|
||||
|
||||
```sh
|
||||
# Örneğin
|
||||
https://api.day.app/key/{push content}
|
||||
|
||||
# Eğer {anlık bildirim içeriği} ise
|
||||
"a/b/c/"
|
||||
|
||||
# O zaman URLnin son durumu şöyledir
|
||||
https://api.day.app/key/a/b/c/
|
||||
# İlgili rota bulunamaz ve arka uç programı 404 döndürür
|
||||
|
||||
#Eklemeden önce {anlık bildirim içeriği} öğesini url kodlamalısınız
|
||||
https://api.day.app/key/a%2Fb%2Fc%2F
|
||||
```
|
||||
Gelişmiş bir HTTP kütüphanesi kullanırsanız, parametreler otomatik olarak işlenecek ve manuel kodlamaya ihtiyaç duymayacaksınız. <br>
|
||||
Ancak URL'yi kendiniz eklerseniz, parametrelerdeki özel karakterler için özel dikkat göstermeniz gerekir. **Özel karakterlerin varlığını dikkate almadan ve otomatik olarak URL kodlama katmanı uygulamak genellikle daha iyi bir yaklaşımdır.**
|
||||
|
||||
#### Gizlilik ve güvenlik nasıl sağlanır?
|
||||
Bakınız [gizlilik güvenlik](/tr/privacy)
|
||||
31
docs/tr/privacy.md
Normal file
31
docs/tr/privacy.md
Normal file
@ -0,0 +1,31 @@
|
||||
#### Gizlilik Nasıl Sızdırılır? <!-- {docsify-ignore-all} -->
|
||||
Bir anlık bildirimin gönderilmesinden alınmasına kadar geçtiği yol şöyledir:<br />
|
||||
Gönderici<font color='red'> → Sunucu①</font> → Apple APNS sunucusu → Aygıtınız → <font color='red'>Bark Uygulaması②</font>。
|
||||
|
||||
Kırmızıyla işaretlenen bu iki yerde gizlilik sızdırma riski olabilir:<br>
|
||||
* Gönderen HTTPS kullanmıyor veya herkese açık bir sunucu kullanıyor (yazarlar istek günlüklerini görecektir)
|
||||
* Bark Uygulaması kendisi güvenli değilse, App Store'a yüklenen sürümde değişiklikler yapılmış olabilir.
|
||||
|
||||
#### Sunucu Gizlilik Sorununu Nasıl Çözebilirsiniz
|
||||
* Açık kaynaklı sunucu kodunu kullanarak [kendi sunucunuzu kurarak](/tr/deploy.md) ve HTTPS'yi etkinleştirerek.
|
||||
* [Şifreli Push Bildirimi](/tr/encryption) kullanarak, içerikleri şifreleyebilirsiniz.
|
||||
|
||||
#### Uygulamanın Tamamen Açık Kaynak Kodlarla İnşa Edildiğini Sağlama
|
||||
Uygulamanın güvenli olmasını ve hiç kimse tarafından (yazar dahil) değiştirilmemesini sağlamak için Bark, GitHub Actions tarafından oluşturulur ve ardından App Store'a yüklenir.<br />
|
||||
GitHub Çalıştırma Kimliği, Bark uygulama ayarlarında bulunabilir; burada derlemenin geçerli sürümü için kullanılan yapılandırma dosyası, derlemede kullanılan kaynak kodu, App Store'a yüklenen sürümün derleme numarası ve daha fazlası hakkında bilgi bulabilirsiniz.<br>
|
||||
|
||||
|
||||
Aynı derleme numarası App Store'a yalnızca bir kez yüklenebilir, bu nedenle benzersizdir.<br>
|
||||
Bu numarayı, Mağazadan indirilen Bark Uygulamalarını karşılaştırmak için kullanabilirsiniz ve eşleşirse, App Store'dan indirilen Uygulamanın tamamen açık kaynak kodundan oluşturulduğunu kanıtlar.
|
||||
|
||||
Örnek: Bark 1.2.9 - 3<br>
|
||||
https://github.com/Finb/Bark/actions/runs/3327969456
|
||||
|
||||
1. Derleme sırasındaki commit kimliğini bulun ve derleme sırasında kullanılan kaynak kodlarını görüntüleyin.
|
||||
2. .github/workflows/testflight.yaml dosyasını inceleyin, tüm işlemleri doğrulayın ve bu işlemlerin kayıtlarının değiştirilmediğinden emin olun.
|
||||
3. Action Günlüklerini görüntüleyin: https://github.com/Finb/Bark/actions/runs/3327969456/jobs/5503414528
|
||||
4. Paketlenen Uygulama Kimliği (App ID), Ekip Kimliği (Team ID), App Store'a yüklenen sürüm ve derleme numarası gibi bilgileri bulun.
|
||||
5. Mağaza için ilgili sürümün IPA dosyasını indirin ve derleme numarasını kayıtlardakiyle karşılaştırın *(Bu numara her uygulama için benzersizdir ve bir kez yüklendikten sonra aynı derleme numarasıyla başka bir sürüm yüklenemez)*.
|
||||
|
||||
|
||||
*Bu kapsamda iOS'un gizlilik açısından incelenmesi dikkate alınmamıştır*
|
||||
71
docs/tr/tutorial.md
Normal file
71
docs/tr/tutorial.md
Normal file
@ -0,0 +1,71 @@
|
||||
## Anlık Bildirim Gönderimi
|
||||
1. Uygulamayı açın, test URL'sini kopyalayın.
|
||||
|
||||
<img src="_media/example.jpg" width=365 />
|
||||
|
||||
2. İçeriği değiştirin ve URL'ye istek atın.<br>
|
||||
GET veya POST isteği gönderebilirsiniz, istek başarılıysa hemen bir push alacaksınız.
|
||||
|
||||
## URL Formatı
|
||||
URL, bir anlık bildirim anahtarı, "title" parametresi ve "body" parametresinden oluşur. İki farklı kombinasyon şekli vardır:
|
||||
|
||||
```
|
||||
/:key/:body
|
||||
/:key/:title/:body
|
||||
```
|
||||
|
||||
## İstek Yöntemi
|
||||
##### GET isteği parametreleri URL'nin sonuna eklenir, örneğin:
|
||||
```sh
|
||||
curl https://api.day.app/your_key/BildirimIcerigi?group=Grup©=KopyalanacakIcerik
|
||||
```
|
||||
*Elle parametreleri URL'ye eklerken URL kodlama sorunlarına dikkat etmelisiniz, [Sıkça Sorulan Sorular: URL Kodlama](tr/faq?id=%C3%96zel-karakterler-anl%C4%B1k-bildirim-i%C5%9Fleminde-ba%C5%9Far%C4%B1s%C4%B1zl%C4%B1%C4%9Fa-neden-oluyor-%C3%96rne%C4%9Fin-%C4%B0tme-i%C3%A7eri%C4%9Fi-ba%C4%9Flant%C4%B1-i%C3%A7eriyor-veya-gibi-%C3%B6zel-karakterler-bo%C5%9Flu%C4%9Fa-d%C3%B6n%C3%BC%C5%9F%C3%BCyor)*
|
||||
|
||||
##### POST isteği parametreleri istek gövdesine yerleştirilir, örneğin:
|
||||
```sh
|
||||
curl -X POST https://api.day.app/your_key \
|
||||
-d'body=Push İçeriği&group=Grup©=Kopyala'
|
||||
```
|
||||
##### POST isteği JSON'ı destekler, örneğin:
|
||||
```sh
|
||||
curl -X "POST" "https://api.day.app/your_key" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d $'{
|
||||
"body": "Test Bark Server",
|
||||
"title": "Test Başlık",
|
||||
"badge": 1,
|
||||
"category": "benimBildirimGrubum",
|
||||
"sound": "minuet.caf",
|
||||
"icon": "https://day.app/assets/images/avatar.jpg",
|
||||
"group": "test",
|
||||
"url": "https://mritd.com"
|
||||
}'
|
||||
```
|
||||
|
||||
##### JSON isteği anahtarı istek gövdesine yerleştirilebilir, URL yolu */push* olmalıdır, örneğin
|
||||
```sh
|
||||
curl -X "POST" "https://api.day.app/push" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d $'{
|
||||
"body": "Test Bark Server",
|
||||
"title": "Test Başlık",
|
||||
"device_key": "sizin_anahtarınız"
|
||||
}'
|
||||
```
|
||||
|
||||
## İstek Parametreleri
|
||||
Desteklenen parametrelerin listesi, belirli bir etkiyi uygulamada nasıl görüneceğini görmek için uygulama içinden önizleme yapabilirsiniz.
|
||||
|
||||
| Parametre | Açıklama |
|
||||
| ----- | ----------- |
|
||||
| title | Anlık bildirim başlığı |
|
||||
| body | Anlık bildirim içeriği |
|
||||
| level | Anlık bildirim kesme seviyesi. <br>**active:** Varsayılan değer, sistem bildirimi hemen göstermek için ekranı aydınlatacaktır.<br>**timeSensitive:** Zamana duyarlı bildirim, odaklanmış durumda bildirim gösterebilir.<br>**passive:** Bildirimi yalnızca bildirim listesine ekler, hatırlatmak için ekranı aydınlatmaz. |
|
||||
| badge | Anlık bildirim rozeti, herhangi bir sayı olabilir. |
|
||||
| autoCopy | iOS 14.5'ten önce otomatik olarak anlık bildirim içeriğini kopyalar, iOS 14.5'ten sonraysa manuel olarak uzun basmalı veya anlık bildirim aşağı çekilmelidir |
|
||||
| copy | Bir anlık bildirim kopyalanırken, kopyalanacak içeriği belirtin; bu parametre belirtilmezse tüm anlık bildirim içeriğini kopyalar. |
|
||||
| sound | Anlık bildirim için bir ses seçebilirsiniz. |
|
||||
| icon | Anlık bildirim için özel bir simge ayarlayın ve ayarlanan simge varsayılan Bark simgesinin yerini alacaktır. Simgeler otomatik olarak yerel olarak önbelleğe alınır ve aynı simge URL'si yalnızca bir kez indirilir. |
|
||||
| group | Bildirimleri gruplandırın, anlık bildirimler gruplanmış bir şekilde bildirim merkezinde görüntülenir.<br>Ayrıca geçmiş mesajlar listesinde farklı grupları görüntülemeyi de seçebilirsiniz. |
|
||||
| isArchive | 1 ile gönderirseniz, anlık bildirim kaydedilir; diğer bir değer gönderirseniz kaydedilmez. Göndermezseniz, kaydetme ayarlarına göre karar verilir. |
|
||||
| url | Anlık bildirime tıklanınca gidilecek URL, URL Şeması ve Evrensel Bağlantıları destekler. |
|
||||
71
docs/tutorial.md
Normal file
71
docs/tutorial.md
Normal file
@ -0,0 +1,71 @@
|
||||
## 发送推送
|
||||
1. 打开APP,复制测试URL
|
||||
|
||||
<img src="../_media/example.jpg" width=365 />
|
||||
|
||||
2. 修改内容,请求这个URL。<br>
|
||||
可以发 GET 或者 POST 请求 ,请求成功会立即收到推送
|
||||
|
||||
## URL格式
|
||||
URL由推送key、参数 title、参数 body 组成。有下面两种组合方式
|
||||
|
||||
```
|
||||
/:key/:body
|
||||
/:key/:title/:body
|
||||
```
|
||||
|
||||
## 请求方式
|
||||
##### GET 请求参数拼接在 URL 后面,例如:
|
||||
```sh
|
||||
curl https://api.day.app/your_key/推送内容?group=分组©=复制
|
||||
```
|
||||
*手动拼接参数到URL上时,请注意URL编码问题,可以参考阅读[常见问题:URL编码](/faq?id=%e6%8e%a8%e9%80%81%e7%89%b9%e6%ae%8a%e5%ad%97%e7%ac%a6%e5%af%bc%e8%87%b4%e6%8e%a8%e9%80%81%e5%a4%b1%e8%b4%a5%ef%bc%8c%e6%af%94%e5%a6%82-%e6%8e%a8%e9%80%81%e5%86%85%e5%ae%b9%e5%8c%85%e5%90%ab%e9%93%be%e6%8e%a5%ef%bc%8c%e6%88%96%e6%8e%a8%e9%80%81%e5%bc%82%e5%b8%b8-%e6%af%94%e5%a6%82-%e5%8f%98%e6%88%90%e7%a9%ba%e6%a0%bc)*
|
||||
|
||||
##### POST 请求参数放在请求体中,例如:
|
||||
```sh
|
||||
curl -X POST https://api.day.app/your_key \
|
||||
-d'body=推送内容&group=分组©=复制'
|
||||
```
|
||||
##### POST 请求支持JSON,例如:
|
||||
```sh
|
||||
curl -X "POST" "https://api.day.app/your_key" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d $'{
|
||||
"body": "Test Bark Server",
|
||||
"title": "Test Title",
|
||||
"badge": 1,
|
||||
"category": "myNotificationCategory",
|
||||
"sound": "minuet.caf",
|
||||
"icon": "https://day.app/assets/images/avatar.jpg",
|
||||
"group": "test",
|
||||
"url": "https://mritd.com"
|
||||
}'
|
||||
```
|
||||
|
||||
##### JSON 请求 key 可以放进请求体中,URL 路径须为 /push,例如
|
||||
```sh
|
||||
curl -X "POST" "https://api.day.app/push" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d $'{
|
||||
"body": "Test Bark Server",
|
||||
"title": "Test Title",
|
||||
"device_key": "your_key"
|
||||
}'
|
||||
```
|
||||
|
||||
## 请求参数
|
||||
支持的参数列表,具体效果可在APP内预览。
|
||||
|
||||
| 参数 | 说明 |
|
||||
| ----- | ----------- |
|
||||
| title | 推送标题 |
|
||||
| body | 推送内容 |
|
||||
| level | 推送中断级别。 <br>active:默认值,系统会立即亮屏显示通知<br>timeSensitive:时效性通知,可在专注状态下显示通知。<br>passive:仅将通知添加到通知列表,不会亮屏提醒。 |
|
||||
| badge | 推送角标,可以是任意数字 |
|
||||
| autoCopy | iOS14.5以下自动复制推送内容,iOS14.5以上需手动长按推送或下拉推送 |
|
||||
| copy | 复制推送时,指定复制的内容,不传此参数将复制整个推送内容。 |
|
||||
| sound | 可以为推送设置不同的铃声 |
|
||||
| icon | 为推送设置自定义图标,设置的图标将替换默认Bark图标。<br>图标会自动缓存在本机,相同的图标 URL 仅下载一次。 |
|
||||
| group | 对消息进行分组,推送将按group分组显示在通知中心中。<br>也可在历史消息列表中选择查看不同的群组。 |
|
||||
| isArchive | 传 1 保存推送,传其他的不保存推送,不传按APP内设置来决定是否保存。 |
|
||||
| url | 点击推送时,跳转的URL ,支持URL Scheme 和 Universal Link |
|
||||
Loading…
x
Reference in New Issue
Block a user