diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 296c0221cd..cb15e36c09 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -46,7 +46,7 @@ assignees: '' - [ ] Create a [Github Release](https://github.com/planetary-social/planetary-ios/releases/new), copying in the tag name and a link to the CHANGELOG. - [ ] Click "Release this Version" in [App Store Connect](https://appstoreconnect.apple.com/apps/1481617318/appstore/ios/version/inflight) - [ ] Merge the release branch into `main` and delete it. -- [ ] Post release notes to Planetary account at https://planetary.rocks. (Connect to replicate with `sbot gossip.connect "net:planetary.rocks:8008~shs:l1sGqWeCZRA99gN+t9sI6+UOzGcHq3KhLQUYEwb4DCo="`) +- [ ] Post release notes to Planetary account at https://planetary.rocks. (Connect your machine to replicate by opening the console and running `sbot gossip.connect "net:planetary.rocks:8008~shs:l1sGqWeCZRA99gN+t9sI6+UOzGcHq3KhLQUYEwb4DCo="`) - [ ] Post release notes on [Radaar](https://radaar.io) - [ ] Post release notes on [Discord](https://discord.com/channels/776485686181363729/776485686181363732) - [ ] Update this wiki page with any procedural changes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d98c1d039..a7ca77cb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Redesigned screen that shows the detail of a message. +- Sub-threads: you can now reply to a reply. +- Add the option to reply to a reaction or a contact message (follow, unfollow, block). +- The Preview screen shows the message you are replying to (if so). + +## [2.0.1] 2023-03-28 + +- Improved message replication performance. +- Redesigned screen for composing posts with better support for previewing. +- Filter list of followers/follows by name, bio or identity. + +## [2.0.0] 2023-03-08 + - Updated the localization strategy to have a better support of foreign languages. #1065 - Added the option to join the Planetary room to the Manage Rooms screen. #1137 - Add a button to delete the SQL database in the debug settings. #738 diff --git a/Frameworks/GoSSB.xcframework/ios-arm64/libssb-go.a b/Frameworks/GoSSB.xcframework/ios-arm64/libssb-go.a index 7366df4bfb..fb536af537 100644 --- a/Frameworks/GoSSB.xcframework/ios-arm64/libssb-go.a +++ b/Frameworks/GoSSB.xcframework/ios-arm64/libssb-go.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dbd6a2b2441ba55bd1bd0224fe7396bc04c3e605a2d3d76d0d59af4fa15ca4a -size 18246424 +oid sha256:88a235130aa514f3687de0bb9941956683d747a1d7bfe733dcb8f18726dc90bc +size 18148128 diff --git a/Frameworks/GoSSB.xcframework/ios-arm64_x86_64-simulator/libssb-go.a b/Frameworks/GoSSB.xcframework/ios-arm64_x86_64-simulator/libssb-go.a index cdeff9cbb6..20fe37a533 100644 --- a/Frameworks/GoSSB.xcframework/ios-arm64_x86_64-simulator/libssb-go.a +++ b/Frameworks/GoSSB.xcframework/ios-arm64_x86_64-simulator/libssb-go.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a81136932440c4d3a5d1a0cb414061b2d6b33b6bae3503a1479d63048c827a6 -size 35229520 +oid sha256:5c251ee1529724bc4116f15be1b31c5eea53a4ccef3159a0115231425d9fcbdb +size 35031776 diff --git a/GoSSB/Sources/go.mod b/GoSSB/Sources/go.mod index 80bbf384a7..0564b6bf1d 100644 --- a/GoSSB/Sources/go.mod +++ b/GoSSB/Sources/go.mod @@ -4,9 +4,9 @@ require ( github.com/boreq/errors v0.1.0 github.com/dgraph-io/badger/v3 v3.2103.5 github.com/pkg/errors v0.9.1 - github.com/planetary-social/scuttlego v0.0.3 + github.com/planetary-social/scuttlego v0.0.4 github.com/sirupsen/logrus v1.8.1 - github.com/ssbc/go-ssb v0.2.2-0.20230212123438-2cdd828cd8c8 + github.com/ssbc/go-ssb v0.2.2-0.20230308230318-d6db27d1852d github.com/ssbc/go-ssb-multiserver v0.1.5-0.20221019203850-917ae0e23d57 github.com/ssbc/go-ssb-refs v0.5.2 github.com/stretchr/testify v1.8.1 diff --git a/GoSSB/Sources/go.sum b/GoSSB/Sources/go.sum index d68931f759..5b3da52858 100644 --- a/GoSSB/Sources/go.sum +++ b/GoSSB/Sources/go.sum @@ -175,8 +175,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetary-social/scuttlego v0.0.3 h1:Z3Qn+DDUem5WYBARuf6atCMUn+smo5MqFUM818MZb0k= -github.com/planetary-social/scuttlego v0.0.3/go.mod h1:6317yJFAMsnkTvfGkvKvOfq05hCU+2SMc78j3sgVjiU= +github.com/planetary-social/scuttlego v0.0.4 h1:54rsM2Aj/5KA7QixRPsca0pYDQVoQp56SjQq4Spg5dI= +github.com/planetary-social/scuttlego v0.0.4/go.mod h1:4JQ1EHILc5UmSD86fEhNuy3pyFs4HmMhpce/YCSuX8g= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -215,8 +215,8 @@ github.com/ssbc/go-netwrap v0.1.5-0.20221019160355-cd323bb2e29d/go.mod h1:tsE1qe github.com/ssbc/go-secretstream v1.2.11-0.20221019175226-fa042d4912fe/go.mod h1:imXhXNa5OfEL+qrGtOs6NZ9zJe6L3P+ZwFVC2mIgH0E= github.com/ssbc/go-secretstream v1.2.11-0.20221111164233-4b41f899f844 h1:r1uKQOpTliDf9BCMbRfCeynZ87Y+XMs/DBZqZzB596Y= github.com/ssbc/go-secretstream v1.2.11-0.20221111164233-4b41f899f844/go.mod h1:imXhXNa5OfEL+qrGtOs6NZ9zJe6L3P+ZwFVC2mIgH0E= -github.com/ssbc/go-ssb v0.2.2-0.20230212123438-2cdd828cd8c8 h1:p1Nwim4mUOrsY/iXlu2m/Ctk0p6GjHJNg4WUqIygEjY= -github.com/ssbc/go-ssb v0.2.2-0.20230212123438-2cdd828cd8c8/go.mod h1:kBucAtyavzNPi81r37zkYSa569sOvvO2nwvzMUexG3w= +github.com/ssbc/go-ssb v0.2.2-0.20230308230318-d6db27d1852d h1:3jzEBxJe5nqHhaIlM984T52YPsRwbNrwuXpkQsllAtQ= +github.com/ssbc/go-ssb v0.2.2-0.20230308230318-d6db27d1852d/go.mod h1:tKx4OFBqFpNCxYUZagBnfqVfLhYtzUM1vJVR3tUkLnw= github.com/ssbc/go-ssb-multiserver v0.1.5-0.20221019203850-917ae0e23d57 h1:wfIu3HcI8HGLSbJFo35eKMjFBMhHhXJsYGTAOVhpNkQ= github.com/ssbc/go-ssb-multiserver v0.1.5-0.20221019203850-917ae0e23d57/go.mod h1:LMJaMYVJxtYKTj8A0t8+iM+K2/SuYSvp14urj9sHLa8= github.com/ssbc/go-ssb-refs v0.5.2-0.20221017100922-8e95413c6580/go.mod h1:rUj40X7iiUWFt62aF4NVm3uNnKiPZ9Uo/ds4D4ZE980= diff --git a/Planetary.xcodeproj/project.pbxproj b/Planetary.xcodeproj/project.pbxproj index 27ca3cb94e..727ef3b08b 100644 --- a/Planetary.xcodeproj/project.pbxproj +++ b/Planetary.xcodeproj/project.pbxproj @@ -560,7 +560,6 @@ 53E341F4224D9E3B002BB5F4 /* URL+Identifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341F3224D9E3B002BB5F4 /* URL+Identifier.swift */; }; 53E341F6224D9FC7002BB5F4 /* AppController+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341F5224D9FC7002BB5F4 /* AppController+URL.swift */; }; 53E341F8224DB1D8002BB5F4 /* BlobViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341F7224DB1D8002BB5F4 /* BlobViewController.swift */; }; - 53E341FC224FF87D002BB5F4 /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341FB224FF87D002BB5F4 /* NewPostViewController.swift */; }; 53E341FE224FF9EE002BB5F4 /* UIImage+Verse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341FD224FF9EE002BB5F4 /* UIImage+Verse.swift */; }; 53E34200225000D4002BB5F4 /* UIViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341FF225000D4002BB5F4 /* UIViewController+Keyboard.swift */; }; 53E34202225008FE002BB5F4 /* Notification+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E34201225008FE002BB5F4 /* Notification+Keyboard.swift */; }; @@ -637,6 +636,12 @@ 5B3FBE3B292BB9E5004F34CC /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3FBE39292BB9E5004F34CC /* ImagePicker.swift */; }; 5B3FBE3D292BE5E0004F34CC /* EditAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3FBE3C292BE5E0004F34CC /* EditAvatarButton.swift */; }; 5B3FBE3E292BE5E0004F34CC /* EditAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3FBE3C292BE5E0004F34CC /* EditAvatarButton.swift */; }; + 5B46611229C9CC60008B8E8C /* CompactMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46611129C9CC60008B8E8C /* CompactMessageView.swift */; }; + 5B46611329C9CC60008B8E8C /* CompactMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46611129C9CC60008B8E8C /* CompactMessageView.swift */; }; + 5B46611629C9D289008B8E8C /* GoldenMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46611529C9D289008B8E8C /* GoldenMessageView.swift */; }; + 5B46611729C9D289008B8E8C /* GoldenMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46611529C9D289008B8E8C /* GoldenMessageView.swift */; }; + 5B46612429CFB537008B8E8C /* LoadingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46612329CFB537008B8E8C /* LoadingCard.swift */; }; + 5B46612529CFB537008B8E8C /* LoadingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46612329CFB537008B8E8C /* LoadingCard.swift */; }; 5B4AEA70294D14240059E039 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4AEA6D294D14240059E039 /* HomeView.swift */; }; 5B4AEA71294D14240059E039 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4AEA6D294D14240059E039 /* HomeView.swift */; }; 5B4AEA72294D14240059E039 /* EmptyHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4AEA6E294D14240059E039 /* EmptyHomeView.swift */; }; @@ -668,6 +673,16 @@ 5B533E56295B85F400F5EED1 /* DiscoverStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B533E55295B85F400F5EED1 /* DiscoverStrategy.swift */; }; 5B533E57295B85F400F5EED1 /* DiscoverStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B533E55295B85F400F5EED1 /* DiscoverStrategy.swift */; }; 5B5BB8DD283E8BFC00D99AB8 /* SkeletonView in Frameworks */ = {isa = PBXBuildFile; productRef = 5B5BB8DC283E8BFC00D99AB8 /* SkeletonView */; }; + 5B5BF56829BA5746003C09A4 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56729BA5746003C09A4 /* ComposeView.swift */; }; + 5B5BF56A29BB7619003C09A4 /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56929BB7619003C09A4 /* PreviewView.swift */; }; + 5B5BF56C29BFE065003C09A4 /* ImagePickerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56B29BFE065003C09A4 /* ImagePickerButton.swift */; }; + 5B5BF56E29C01E5A003C09A4 /* AttachedImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56D29C01E5A003C09A4 /* AttachedImageButton.swift */; }; + 5B5BF57529C2234D003C09A4 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56729BA5746003C09A4 /* ComposeView.swift */; }; + 5B5BF57729C2239C003C09A4 /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56929BB7619003C09A4 /* PreviewView.swift */; }; + 5B5BF57829C2239F003C09A4 /* ImagePickerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56B29BFE065003C09A4 /* ImagePickerButton.swift */; }; + 5B5BF57929C2239F003C09A4 /* AttachedImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF56D29C01E5A003C09A4 /* AttachedImageButton.swift */; }; + 5B5BF57F29C4D1C5003C09A4 /* LikeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF57E29C4D1C5003C09A4 /* LikeButton.swift */; }; + 5B5BF58029C4D1C5003C09A4 /* LikeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5BF57E29C4D1C5003C09A4 /* LikeButton.swift */; }; 5B5EF4F7294FDA460052237A /* InfiniteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5EF4F6294FDA460052237A /* InfiniteDataSource.swift */; }; 5B5EF4F8294FDA460052237A /* InfiniteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5EF4F6294FDA460052237A /* InfiniteDataSource.swift */; }; 5B5EF4FB294FE1230052237A /* MessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5EF4FA294FE1230052237A /* MessageList.swift */; }; @@ -758,6 +773,12 @@ 5BC2975D2806024B00C0CD81 /* PostsAndContactsAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2975C2806024B00C0CD81 /* PostsAndContactsAlgorithm.swift */; }; 5BC2975E2806024B00C0CD81 /* PostsAndContactsAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2975C2806024B00C0CD81 /* PostsAndContactsAlgorithm.swift */; }; 5BC3DE6128299CB900F6A363 /* SocialStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3DE6028299CB900F6A363 /* SocialStats.swift */; }; + 5BC7F4FD29997900007D5566 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC7F4FC29997900007D5566 /* MessageView.swift */; }; + 5BC7F4FE29997900007D5566 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC7F4FC29997900007D5566 /* MessageView.swift */; }; + 5BC7F5042999ADAC007D5566 /* RepliesStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC7F5032999ADAC007D5566 /* RepliesStrategy.swift */; }; + 5BC7F5052999ADAC007D5566 /* RepliesStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC7F5032999ADAC007D5566 /* RepliesStrategy.swift */; }; + 5BC7F5082999BCC9007D5566 /* CompactVoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC7F5072999BCC9007D5566 /* CompactVoteView.swift */; }; + 5BC7F5092999BCC9007D5566 /* CompactVoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC7F5072999BCC9007D5566 /* CompactVoteView.swift */; }; 5BE28DFD27CD7BA1004D7D27 /* Analytics in Frameworks */ = {isa = PBXBuildFile; productRef = 5BE28DFC27CD7BA1004D7D27 /* Analytics */; }; 5BE69B0D27CEA1B70013D51D /* Analytics in Frameworks */ = {isa = PBXBuildFile; productRef = 5BE69B0C27CEA1B70013D51D /* Analytics */; }; 5BE8A30929193ECE00C4A38E /* BlobGalleryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE8A30829193ECE00C4A38E /* BlobGalleryView.swift */; }; @@ -956,7 +977,6 @@ C9724B582809D5F8000EBCCD /* PostCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4871002498030800BCD063 /* PostCollectionViewCell.swift */; }; C9724B592809D5FE000EBCCD /* UIEdgeInsets+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5396A6202244312400C57A4B /* UIEdgeInsets+Layout.swift */; }; C9724B5A2809D60B000EBCCD /* UIViewController+Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536255DD23AC4C46001007D0 /* UIViewController+Sync.swift */; }; - C9724B5B2809D614000EBCCD /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E341FB224FF87D002BB5F4 /* NewPostViewController.swift */; }; C9724B5D2809D62A000EBCCD /* MessagePaginatedCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4870FE249801CD00BCD063 /* MessagePaginatedCollectionViewDataSource.swift */; }; C9724B5E2809D651000EBCCD /* NSMutableAttributedString+Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531B92BA22CD1383005D5255 /* NSMutableAttributedString+Attributes.swift */; }; C9724B5F2809D664000EBCCD /* UIScreen+Sizes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D768CEB2321A38800B6EC29 /* UIScreen+Sizes.swift */; }; @@ -1610,7 +1630,6 @@ 53E341F3224D9E3B002BB5F4 /* URL+Identifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Identifier.swift"; sourceTree = ""; }; 53E341F5224D9FC7002BB5F4 /* AppController+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppController+URL.swift"; sourceTree = ""; }; 53E341F7224DB1D8002BB5F4 /* BlobViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlobViewController.swift; sourceTree = ""; }; - 53E341FB224FF87D002BB5F4 /* NewPostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPostViewController.swift; sourceTree = ""; }; 53E341FD224FF9EE002BB5F4 /* UIImage+Verse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Verse.swift"; sourceTree = ""; }; 53E341FF225000D4002BB5F4 /* UIViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Keyboard.swift"; sourceTree = ""; }; 53E34201225008FE002BB5F4 /* Notification+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Keyboard.swift"; sourceTree = ""; }; @@ -1655,6 +1674,9 @@ 5B3FBE3029295CEC004F34CC /* BioView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BioView.swift; sourceTree = ""; }; 5B3FBE39292BB9E5004F34CC /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 5B3FBE3C292BE5E0004F34CC /* EditAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAvatarButton.swift; sourceTree = ""; }; + 5B46611129C9CC60008B8E8C /* CompactMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactMessageView.swift; sourceTree = ""; }; + 5B46611529C9D289008B8E8C /* GoldenMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoldenMessageView.swift; sourceTree = ""; }; + 5B46612329CFB537008B8E8C /* LoadingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCard.swift; sourceTree = ""; }; 5B4AEA6D294D14240059E039 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 5B4AEA6E294D14240059E039 /* EmptyHomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyHomeView.swift; sourceTree = ""; }; 5B4AEA75294D14320059E039 /* EmptyPostsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyPostsView.swift; sourceTree = ""; }; @@ -1669,6 +1691,11 @@ 5B533E4F295B5D0500F5EED1 /* InfiniteGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfiniteGrid.swift; sourceTree = ""; }; 5B533E52295B5F8200F5EED1 /* MessageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageGrid.swift; sourceTree = ""; }; 5B533E55295B85F400F5EED1 /* DiscoverStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverStrategy.swift; sourceTree = ""; }; + 5B5BF56729BA5746003C09A4 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; }; + 5B5BF56929BB7619003C09A4 /* PreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = ""; }; + 5B5BF56B29BFE065003C09A4 /* ImagePickerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerButton.swift; sourceTree = ""; }; + 5B5BF56D29C01E5A003C09A4 /* AttachedImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachedImageButton.swift; sourceTree = ""; }; + 5B5BF57E29C4D1C5003C09A4 /* LikeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeButton.swift; sourceTree = ""; }; 5B5EF4F6294FDA460052237A /* InfiniteDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfiniteDataSource.swift; sourceTree = ""; }; 5B5EF4FA294FE1230052237A /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = ""; }; 5B67FFDC2863B4F40028ABE4 /* NumberOfRecentItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberOfRecentItemsOperation.swift; sourceTree = ""; }; @@ -1716,6 +1743,9 @@ 5BC297582806022C00C0CD81 /* PostsAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsAlgorithm.swift; sourceTree = ""; }; 5BC2975C2806024B00C0CD81 /* PostsAndContactsAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsAndContactsAlgorithm.swift; sourceTree = ""; }; 5BC3DE6028299CB900F6A363 /* SocialStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialStats.swift; sourceTree = ""; }; + 5BC7F4FC29997900007D5566 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; + 5BC7F5032999ADAC007D5566 /* RepliesStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepliesStrategy.swift; sourceTree = ""; }; + 5BC7F5072999BCC9007D5566 /* CompactVoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactVoteView.swift; sourceTree = ""; }; 5BE8A30829193ECE00C4A38E /* BlobGalleryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlobGalleryView.swift; sourceTree = ""; }; 5BE8A30C2919C9E800C4A38E /* IdentityListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityListView.swift; sourceTree = ""; }; 5BE8A30F291A96FE00C4A38E /* IdentityOptionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityOptionsButton.swift; sourceTree = ""; }; @@ -2172,7 +2202,6 @@ 53B634B322150A4100400403 /* MainViewController.swift */, 0AD8D630240EC38600D87A95 /* ManagePubsViewController.swift */, 5319EBD622F22BA700EC7583 /* MenuViewController.swift */, - 53E341FB224FF87D002BB5F4 /* NewPostViewController.swift */, 53B4F5F022B7123900027C6A /* NotificationsViewController.swift */, 5B1FF5E428D8AA25008F3A85 /* RawMessageController.swift */, 0AD8D634240EFF8800D87A95 /* RedeemInviteViewController.swift */, @@ -2486,6 +2515,7 @@ 5396A63622483B7900C57A4B /* UI */ = { isa = PBXGroup; children = ( + 5B5BF56529BA573B003C09A4 /* Compose */, 5B18DB772968B536001F3B70 /* Search */, 5B533E4B295B5B9B00F5EED1 /* Discover */, 5B2CF3982953A01F00630CB6 /* Message */, @@ -2767,6 +2797,11 @@ 5B533E52295B5F8200F5EED1 /* MessageGrid.swift */, 5BAB1840290C2634009F8F90 /* CompactPostView.swift */, 5B747F6F295E1AA8003D014A /* GoldenPostView.swift */, + 5BC7F4FC29997900007D5566 /* MessageView.swift */, + 5BC7F5072999BCC9007D5566 /* CompactVoteView.swift */, + 5B5BF57E29C4D1C5003C09A4 /* LikeButton.swift */, + 5B46611129C9CC60008B8E8C /* CompactMessageView.swift */, + 5B46611529C9D289008B8E8C /* GoldenMessageView.swift */, ); path = Message; sourceTree = ""; @@ -2800,6 +2835,17 @@ path = Discover; sourceTree = ""; }; + 5B5BF56529BA573B003C09A4 /* Compose */ = { + isa = PBXGroup; + children = ( + 5B5BF56729BA5746003C09A4 /* ComposeView.swift */, + 5B5BF56929BB7619003C09A4 /* PreviewView.swift */, + 5B5BF56B29BFE065003C09A4 /* ImagePickerButton.swift */, + 5B5BF56D29C01E5A003C09A4 /* AttachedImageButton.swift */, + ); + path = Compose; + sourceTree = ""; + }; 5B7AB67D28F46E90007DCCF1 /* Identity */ = { isa = PBXGroup; children = ( @@ -2853,6 +2899,7 @@ 5B2CF37D2950CE3100630CB6 /* HomeStrategy.swift */, 5B2CF3902952343B00630CB6 /* ProfileStrategy.swift */, 5B533E55295B85F400F5EED1 /* DiscoverStrategy.swift */, + 5BC7F5032999ADAC007D5566 /* RepliesStrategy.swift */, ); path = FeedStrategy; sourceTree = ""; @@ -3102,6 +3149,7 @@ E0A4AB20294A52C300A410CB /* RoomCard.swift */, 5B88E5FA29707A54005F865D /* CardStyle.swift */, 5B88E5FD29707D8F005F865D /* CardButtonStyle.swift */, + 5B46612329CFB537008B8E8C /* LoadingCard.swift */, ); path = Cards; sourceTree = ""; @@ -3813,6 +3861,7 @@ 2D1C46F0289DCA08003ED254 /* DiscoveryFeedStrategySelectionViewController.swift in Sources */, C9724B2A2809D47D000EBCCD /* BackupOnboardingStep.swift in Sources */, C9724B2B2809D47D000EBCCD /* BenefitsOnboardingStep.swift in Sources */, + 5B5BF57829C2239F003C09A4 /* ImagePickerButton.swift in Sources */, C9724B2C2809D47D000EBCCD /* BioOnboardingStep.swift in Sources */, C9724B612809D680000EBCCD /* UICollectionView+Verse.swift in Sources */, C9724B2D2809D47D000EBCCD /* BirthdateOnboardingStep.swift in Sources */, @@ -3832,6 +3881,7 @@ C9EBE0482880C2C900A2CF51 /* HelpDrawerCoordinator.swift in Sources */, C982CBE228353E2600D8963F /* ContactHeaderView.swift in Sources */, C9724B332809D47D000EBCCD /* NameOnboardingStep.swift in Sources */, + 5B46611729C9D289008B8E8C /* GoldenMessageView.swift in Sources */, 5BA6E3CD2939A96C000393AC /* FloatingButton.swift in Sources */, C9724B4E2809D594000EBCCD /* NotificationsViewController.swift in Sources */, C9724B8A2809E971000EBCCD /* AttributedStringCache.swift in Sources */, @@ -3849,6 +3899,7 @@ C9724BB92809EC7B000EBCCD /* BotViewController.swift in Sources */, C9421EB72888A43600A4C86D /* URL+Planetary.swift in Sources */, C9724B362809D47D000EBCCD /* PhotoConfirmOnboardingStep.swift in Sources */, + 5B5BF58029C4D1C5003C09A4 /* LikeButton.swift in Sources */, C9724BBB2809ECA8000EBCCD /* Saveable.swift in Sources */, 5BFC1C2528FF0859008A4B87 /* ExtendedSocialStats.swift in Sources */, C9724B372809D47D000EBCCD /* PhotoOnboardingStep.swift in Sources */, @@ -3856,6 +3907,7 @@ C9724B7C2809E8D4000EBCCD /* PillButton.swift in Sources */, C982CBDE28353DBB00D8963F /* JoinPlanetarySystemOperation.swift in Sources */, C9724B382809D47D000EBCCD /* SplashOnboardingStep.swift in Sources */, + 5BC7F5092999BCC9007D5566 /* CompactVoteView.swift in Sources */, 53E29CE122D80427008A2CB1 /* Hashtag+NSAttributedString.swift in Sources */, 0A64A83924735392009A5EBF /* PushAPIService.swift in Sources */, 53C2B13C2295BC940018D0A8 /* Keychain.swift in Sources */, @@ -3871,6 +3923,7 @@ 5B97D1E629635FFF000AA5D1 /* SearchResultsView.swift in Sources */, C9724B742809E7A6000EBCCD /* UIFont+Verse.swift in Sources */, C969F0E12899CFEE00081615 /* Room.swift in Sources */, + 5B46612529CFB537008B8E8C /* LoadingCard.swift in Sources */, C9724BE22809EE7D000EBCCD /* SyncOperation.swift in Sources */, C9724B102809D1D6000EBCCD /* AppController.swift in Sources */, 533EDBF52389BDD4008B3565 /* UIColor+Hex.swift in Sources */, @@ -3923,6 +3976,7 @@ C9724BC82809ED75000EBCCD /* UINavigationBar+Verse.swift in Sources */, C9724BDA2809EE32000EBCCD /* SendMissionOperation.swift in Sources */, 0ACE91A8243D748700EFB4E9 /* GoBotError+LocalizedError.swift in Sources */, + 5BC7F4FE29997900007D5566 /* MessageView.swift in Sources */, C9724B842809E938000EBCCD /* PostCellView.swift in Sources */, C9724BD62809EE12000EBCCD /* ConnectedPeerListView.swift in Sources */, 2358696224A0FB9100F4FC1D /* URLRequest+APIHeaders.swift in Sources */, @@ -3951,6 +4005,7 @@ C9724BB72809EC68000EBCCD /* (null) in Sources */, 0A64A84024735520009A5EBF /* NullPushAPI.swift in Sources */, 23EF5AAA249D177F00469977 /* BanListAPI.swift in Sources */, + 5B5BF57729C2239C003C09A4 /* PreviewView.swift in Sources */, 53E26C5E23045D34009240B2 /* Data+Person.swift in Sources */, 23C7A47922748E8700F02311 /* URL+Identifier.swift in Sources */, C9724B932809EA18000EBCCD /* SmallPostHeaderView.swift in Sources */, @@ -3993,6 +4048,7 @@ C9C3788C27C6CC6900238B58 /* Publisher+collectNext.swift in Sources */, C9724B5F2809D664000EBCCD /* UIScreen+Sizes.swift in Sources */, 53631EF623A9A93F009C6999 /* Blob+String.swift in Sources */, + 5BC7F5052999ADAC007D5566 /* RepliesStrategy.swift in Sources */, C9724BC42809ED5B000EBCCD /* Blob+UIColor.swift in Sources */, C9412AEB28AEE8EB00F791A7 /* RoomAliasRegistrationController.swift in Sources */, 5B4AEA7E294D14320059E039 /* CompactHashtagView.swift in Sources */, @@ -4075,6 +4131,7 @@ 236BCB7222689406006DF05B /* Address.swift in Sources */, 5B7AB68028F46EB1007DCCF1 /* ExtendedSocialStatsView.swift in Sources */, C9D1EAD02832A0230092E098 /* MarkdownTests.swift in Sources */, + 5B5BF57929C2239F003C09A4 /* AttachedImageButton.swift in Sources */, 53C2B13D2295BCC10018D0A8 /* Bundle+Version.swift in Sources */, C9724BCE2809EDD4000EBCCD /* AppDelegate+Repair.swift in Sources */, C9D1EACE2832A0230092E098 /* EncodeJSONtest.swift in Sources */, @@ -4094,6 +4151,7 @@ 5B373839288B09A30017756B /* ExtendedAboutCellView.swift in Sources */, C9A2DE3527D0038700EAFA73 /* UIViewController+Alert.swift in Sources */, C9724BDC2809EE3C000EBCCD /* SuspendOperation.swift in Sources */, + 5B5BF57529C2234D003C09A4 /* ComposeView.swift in Sources */, 5316E95F21FFEB130053832E /* Post.swift in Sources */, 5336611622D96AD500100707 /* Dictionary+JSONSerialization.swift in Sources */, C9724BDF2809EE4B000EBCCD /* Layout+SpacerView.swift in Sources */, @@ -4128,6 +4186,7 @@ 238ED6B2232282D600E054A3 /* Blob.swift in Sources */, C9724B8E2809E9B2000EBCCD /* UIViewController+ApplicationWillEnterForeground.swift in Sources */, C9724B762809E7B2000EBCCD /* BlockButton.swift in Sources */, + 5B46611329C9CC60008B8E8C /* CompactMessageView.swift in Sources */, 535754F522692B29002A6989 /* ContentType.swift in Sources */, C969F0E52899D1FD00081615 /* AppDelegate+URLScheme.swift in Sources */, 5BA6E3CA29397CA9000393AC /* LoadingView.swift in Sources */, @@ -4152,7 +4211,6 @@ 0ABCA9082437C16200D7F39C /* NSAttributedString+Markdown.swift in Sources */, C9724B702809E77A000EBCCD /* AvatarStackView.swift in Sources */, C9724BA02809EAB8000EBCCD /* UnfollowOperation.swift in Sources */, - C9724B5B2809D614000EBCCD /* NewPostViewController.swift in Sources */, C9724B422809D4E9000EBCCD /* Layout+FillSuperview.swift in Sources */, 53EE01FB2205035800DFDF16 /* XCTestCase+JSON.swift in Sources */, C9724BEF2809EF45000EBCCD /* UIViewController+TopViewController.swift in Sources */, @@ -4408,6 +4466,8 @@ 5BEE741E2880515800897ACC /* Post+ViewDatabase.swift in Sources */, C95E7C73297898FD00E921F4 /* BotMigrationController.swift in Sources */, 535B6A10237362AE008C248E /* BlockedUsersViewController.swift in Sources */, + 5BC7F5042999ADAC007D5566 /* RepliesStrategy.swift in Sources */, + 5B5BF56829BA5746003C09A4 /* ComposeView.swift in Sources */, 8D74DE012335601F003C284B /* UIViewController+NavigationItems.swift in Sources */, 0A64A82824734F00009A5EBF /* VersePubAPI.swift in Sources */, 539FD4AF22C19B9F005A4DF2 /* AboutsMenu.swift in Sources */, @@ -4420,7 +4480,6 @@ 5BAE9C86281E0EA9008AEA84 /* Support+GoBot.swift in Sources */, 5396A633224836E800C57A4B /* UIColor+Hex.swift in Sources */, 1B5DB7CC24AC01DA008DCB81 /* StatisticsOperation.swift in Sources */, - 53E341FC224FF87D002BB5F4 /* NewPostViewController.swift in Sources */, C9A1632628AE88DC00ACDCC5 /* AddAliasView.swift in Sources */, 5319257E22F286FC00B44FA3 /* URL+Verse.swift in Sources */, 0ACE91A2243D740C00EFB4E9 /* GoBotError.swift in Sources */, @@ -4578,6 +4637,7 @@ 533EDBF023861169008B3565 /* Caches.swift in Sources */, 5BC3DE6128299CB900F6A363 /* SocialStats.swift in Sources */, 5336611122D965B300100707 /* UIColor+Random.swift in Sources */, + 5BC7F5082999BCC9007D5566 /* CompactVoteView.swift in Sources */, C9F1C85527C9875A005A3228 /* Color+Hex.swift in Sources */, 8DA9567D230DA46C00A334EB /* UIButton+Text.swift in Sources */, 53B4F5F122B7123900027C6A /* NotificationsViewController.swift in Sources */, @@ -4597,6 +4657,7 @@ 53375F812321757C00610932 /* GalleryView.swift in Sources */, C9421EB228889F2400A4C86D /* FancySectionTitle.swift in Sources */, 531EC4102310C274001A25AD /* Blob+NSAttributedString.swift in Sources */, + 5B5BF56A29BB7619003C09A4 /* PreviewView.swift in Sources */, 0A143A7B242E6714008745C6 /* AppDelegate+UniversalLink.swift in Sources */, 5B7AB67A28F0926B007DCCF1 /* String+LoremIpsum.swift in Sources */, 0AE5EDBB25826FD7008BDA0C /* String+Emoji.swift in Sources */, @@ -4618,6 +4679,7 @@ 5B7AB66F28E74692007DCCF1 /* ExtendedSocialStats.swift in Sources */, 5BE8A30D2919C9E800C4A38E /* IdentityListView.swift in Sources */, 5B7786D828F876190081B1C6 /* ImageMetadataView.swift in Sources */, + 5B5BF57F29C4D1C5003C09A4 /* LikeButton.swift in Sources */, 8D6D2E64236387C800E7B0EC /* FollowCountView.swift in Sources */, 5BAB1845290C2767009F8F90 /* CompactIdentityView.swift in Sources */, 5B533E53295B5F8200F5EED1 /* MessageGrid.swift in Sources */, @@ -4641,6 +4703,7 @@ 53AD3FE522735C7B005228F9 /* MessageValue.swift in Sources */, 5376FAE2228D005600411F5B /* UIBarButtonItem+Saveable.swift in Sources */, 53631EF323A99CF0009C6999 /* NSAttributedString+Flatten.swift in Sources */, + 5B5BF56E29C01E5A003C09A4 /* AttachedImageButton.swift in Sources */, 0A64A8552473585B009A5EBF /* AuthyPhoneVerificationAPI.swift in Sources */, 2394335222708B8400D56B94 /* Date+millisecs.swift in Sources */, 5BA6E3C02937907E000393AC /* Notification+Post.swift in Sources */, @@ -4651,6 +4714,7 @@ 5B9C69562888813000229469 /* CountUnreadNotificationsOperation.swift in Sources */, 53AD3FD7226FC4B8005228F9 /* UITableView+Verse.swift in Sources */, 53AD3FDD22712CE0005228F9 /* MessageTableViewDelegate.swift in Sources */, + 5B5BF56C29BFE065003C09A4 /* ImagePickerButton.swift in Sources */, 5B2CF3882950FD1D00630CB6 /* FeedStrategyMessageDataSource.swift in Sources */, 536255DE23AC4C46001007D0 /* UIViewController+Sync.swift in Sources */, 23C7443B2200B12000FB554A /* Content.swift in Sources */, @@ -4669,6 +4733,7 @@ 0A64A84B24735766009A5EBF /* PhoneVerificationResponse.swift in Sources */, 530F018F22DCEC08007EBAE2 /* OnboardingStepView.swift in Sources */, 5B4AEA7D294D14320059E039 /* CompactHashtagView.swift in Sources */, + 5B46611229C9CC60008B8E8C /* CompactMessageView.swift in Sources */, 236761592450681800A97140 /* ViewDatabase+Pagination.swift in Sources */, 5BA32ED82915930000744984 /* ActivityView.swift in Sources */, 53375F83232178DB00610932 /* UIPageControl+Verse.swift in Sources */, @@ -4769,6 +4834,7 @@ 5B18DB7A2968B542001F3B70 /* SearchResultsGrid.swift in Sources */, 0A4870FD2498014F00BCD063 /* UICollectionView+Verse.swift in Sources */, 8D317ECD2357A37C0009E073 /* UIImageView+Fade.swift in Sources */, + 5B46612429CFB537008B8E8C /* LoadingCard.swift in Sources */, C9313A7C289AD9D40093AC47 /* RoomInvitationRedeemer.swift in Sources */, 535754F22268DAB0002A6989 /* BotError.swift in Sources */, 0AB736BC2457753F000190F8 /* AsynchronousOperation.swift in Sources */, @@ -4815,6 +4881,7 @@ 8DE093DC234651E1009E505D /* FollowButton.swift in Sources */, 539246E022A9CCBF00D01EEC /* Person.swift in Sources */, 53C422B721C476B000A314AD /* DebugTableViewController.swift in Sources */, + 5B46611629C9D289008B8E8C /* GoldenMessageView.swift in Sources */, 0A64A82324734EC2009A5EBF /* PubAPIService.swift in Sources */, 53211CE42283717C007FB785 /* Image+UIImage.swift in Sources */, 5B5EF4FB294FE1230052237A /* MessageList.swift in Sources */, @@ -4839,6 +4906,7 @@ 530F01A222DEADD9007EBAE2 /* PhoneOnboardingStep.swift in Sources */, 53EAB398239AD0D700DF5530 /* AttributedStringCache.swift in Sources */, C9C3787F27C691EA00238B58 /* PeerConnectionInfo.swift in Sources */, + 5BC7F4FD29997900007D5566 /* MessageView.swift in Sources */, 53E26C4E23022482009240B2 /* AppDelegate+Push.swift in Sources */, C923DBB1288057EB00569AAB /* HelpDrawerView.swift in Sources */, 531B92BD22CD65ED005D5255 /* UIFont+Verse.swift in Sources */, @@ -4952,7 +5020,7 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = "$(SRCROOT)/UnitTests/Resources/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4984,7 +5052,7 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = "$(SRCROOT)/UnitTests/Resources/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5134,7 +5202,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_COMPILATION_MODE = singlefile; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_STRICT_CONCURRENCY = targeted; SWIFT_VERSION = 5.0; }; name = Debug; @@ -5193,7 +5260,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = RELEASE; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_STRICT_CONCURRENCY = targeted; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; @@ -5206,7 +5272,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Resources/FBTT.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 418; DEVELOPMENT_TEAM = GZCZBKH7MY; EAGER_LINKING = YES; ENABLE_BITCODE = NO; @@ -5221,14 +5287,13 @@ "@executable_path/", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS"; SDKROOT = iphoneos; STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SUPPORTS_MACCATALYST = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -5241,7 +5306,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Resources/FBTT.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 418; DEVELOPMENT_TEAM = GZCZBKH7MY; EAGER_LINKING = YES; ENABLE_BITCODE = NO; @@ -5256,7 +5321,7 @@ "@executable_path/", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS"; SDKROOT = iphoneos; diff --git a/Planetary.xcodeproj/xcshareddata/xcschemes/Planetary.xcscheme b/Planetary.xcodeproj/xcshareddata/xcschemes/Planetary.xcscheme index 948adc5a50..ac6759c242 100644 --- a/Planetary.xcodeproj/xcshareddata/xcschemes/Planetary.xcscheme +++ b/Planetary.xcodeproj/xcshareddata/xcschemes/Planetary.xcscheme @@ -1,7 +1,7 @@ + version = "2.0"> @@ -66,12 +66,17 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" + disablePerformanceAntipatternChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" + debugXPCServices = "NO" debugServiceExtension = "internal" - allowLocationSimulation = "YES"> + allowLocationSimulation = "YES" + viewDebuggingEnabled = "No" + queueDebuggingEnabled = "No"> CFBundleVersion - 415 + 418 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt b/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt index 8a84b0bdf3..4e1368cab0 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt @@ -86,37 +86,37 @@ body: MIT Licence Copyrig… version: 6.6.2 -name: bugsnag-cocoa, nameSpecified: Bugsnag, owner: bugsnag, version: 6.16.7, source: https://github.com/bugsnag/bugsnag-cocoa +name: bugsnag-cocoa, nameSpecified: Bugsnag, owner: bugsnag, version: 6.16.7 -name: CocoaLumberjack, nameSpecified: CocoaLumberjack, owner: CocoaLumberjack, version: 3.7.4, source: https://github.com/CocoaLumberjack/CocoaLumberjack +name: CocoaLumberjack, nameSpecified: CocoaLumberjack, owner: CocoaLumberjack, version: 3.7.4 -name: commonui_sdk_ios, nameSpecified: ZendeskCommonUISDK, owner: zendesk, version: 6.1.3, source: https://github.com/zendesk/commonui_sdk_ios +name: commonui_sdk_ios, nameSpecified: ZendeskCommonUISDK, owner: zendesk, version: 6.1.3 -name: core_sdk_ios, nameSpecified: ZendeskCoreSDK, owner: zendesk, version: 2.6.0, source: https://github.com/zendesk/core_sdk_ios +name: core_sdk_ios, nameSpecified: ZendeskCoreSDK, owner: zendesk, version: 2.6.0 -name: lottie-ios, nameSpecified: Lottie, owner: airbnb, version: 3.3.0, source: https://github.com/airbnb/lottie-ios +name: lottie-ios, nameSpecified: Lottie, owner: airbnb, version: 3.3.0 -name: messaging_sdk_ios, nameSpecified: ZendeskMessagingSDK, owner: zendesk, version: 3.8.4, source: https://github.com/zendesk/messaging_sdk_ios +name: messaging_sdk_ios, nameSpecified: ZendeskMessagingSDK, owner: zendesk, version: 3.8.4 -name: messagingapi_sdk_ios, nameSpecified: ZendeskMessagingAPISDK, owner: zendesk, version: 3.8.4, source: https://github.com/zendesk/messagingapi_sdk_ios +name: messagingapi_sdk_ios, nameSpecified: ZendeskMessagingAPISDK, owner: zendesk, version: 3.8.4 -name: posthog-ios, nameSpecified: PostHog, owner: PostHog, version: 1.4.4, source: https://github.com/PostHog/posthog-ios +name: posthog-ios, nameSpecified: PostHog, owner: PostHog, version: 1.4.4 -name: sdkconfigurations_sdk_ios, nameSpecified: ZendeskSDKConfigurationsSDK, owner: zendesk, version: 1.1.10, source: https://github.com/zendesk/sdkconfigurations_sdk_ios +name: sdkconfigurations_sdk_ios, nameSpecified: ZendeskSDKConfigurationsSDK, owner: zendesk, version: 1.1.10 -name: SkeletonView, nameSpecified: SkeletonView, owner: Juanpe, version: 1.29.2, source: https://github.com/Juanpe/SkeletonView +name: SkeletonView, nameSpecified: SkeletonView, owner: Juanpe, version: 1.29.2 -name: SQLite.swift, nameSpecified: SQLite.swift, owner: stephencelis, version: 0.13.3, source: https://github.com/stephencelis/SQLite.swift +name: SQLite.swift, nameSpecified: SQLite.swift, owner: stephencelis, version: 0.13.3 -name: support_providers_sdk_ios, nameSpecified: ZendeskSupportProvidersSDK, owner: zendesk, version: 5.4.1, source: https://github.com/zendesk/support_providers_sdk_ios +name: support_providers_sdk_ios, nameSpecified: ZendeskSupportProvidersSDK, owner: zendesk, version: 5.4.1 -name: support_sdk_ios, nameSpecified: ZendeskSupportSDK, owner: zendesk, version: 5.4.1, source: https://github.com/zendesk/support_sdk_ios +name: support_sdk_ios, nameSpecified: ZendeskSupportSDK, owner: zendesk, version: 5.4.1 -name: swift-log, nameSpecified: swift-log, owner: apple, version: 1.4.2, source: https://github.com/apple/swift-log +name: swift-log, nameSpecified: swift-log, owner: apple, version: 1.4.2 name: Go-SSB, nameSpecified: , version: body: The MIT License (MIT… add-version-numbers: false -LicensePlist Version: 3.23.4 \ No newline at end of file +LicensePlist Version: 3.17.0 \ No newline at end of file diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/CocoaLumberjack.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/CocoaLumberjack.plist index b8eaa4c693..479ee450d8 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/CocoaLumberjack.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/CocoaLumberjack.plist @@ -8,7 +8,7 @@ FooterText BSD 3-Clause License -Copyright (c) 2010-2022, Deusty, LLC +Copyright (c) 2010-2023, Deusty, LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -21,8 +21,6 @@ Redistribution and use in source and binary forms, with or without modification, THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License - BSD-3-Clause Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/Down.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/Down.plist index b55d8ac6cc..07ea5a7967 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/Down.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/Down.plist @@ -27,16 +27,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -70,16 +66,12 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -109,16 +101,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -145,16 +133,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -183,16 +167,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -223,16 +203,12 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -244,16 +220,12 @@ Copyright (C) 2014-15 John MacFarlane Released under the Creative Commons CC-BY-SA 4.0 license: <http://creativecommons.org/licenses/by-sa/4.0/>. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -287,16 +259,12 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License - MIT Type PSGroupSpecifier FooterText ---------------------------------------- - License - MIT Type PSGroupSpecifier @@ -320,8 +288,6 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/Go-SSB.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/Go-SSB.plist index e3ee643198..7f5a67c096 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/Go-SSB.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/Go-SSB.plist @@ -27,8 +27,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/ImageSlideshow.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/ImageSlideshow.plist index ccd6d77338..7687e95c52 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/ImageSlideshow.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/ImageSlideshow.plist @@ -26,8 +26,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/KeychainSwift.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/KeychainSwift.plist index 4d35d03ee2..18fcc4283f 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/KeychainSwift.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/KeychainSwift.plist @@ -28,8 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/Multipart.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/Multipart.plist index 830df88605..8a67135b79 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/Multipart.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/Multipart.plist @@ -28,8 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/PhoneNumberKit.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/PhoneNumberKit.plist index 0fa8a62faa..72d102b0eb 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/PhoneNumberKit.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/PhoneNumberKit.plist @@ -28,8 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/SQLite.swift.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/SQLite.swift.plist index a4300107e1..b5fb95e563 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/SQLite.swift.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/SQLite.swift.plist @@ -28,8 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/SVProgressHUD.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/SVProgressHUD.plist index 7a2afc2ad6..bd8a06a216 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/SVProgressHUD.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/SVProgressHUD.plist @@ -28,8 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/SkeletonView.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/SkeletonView.plist index 175a7a681d..662543f648 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/SkeletonView.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/SkeletonView.plist @@ -29,8 +29,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftGen.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftGen.plist index 7b85577161..5de266dedb 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftGen.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftGen.plist @@ -28,8 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/bugsnag-cocoa.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/bugsnag-cocoa.plist index 1f14dfff46..bfdf18a64a 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/bugsnag-cocoa.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/bugsnag-cocoa.plist @@ -25,8 +25,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/commonui_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/commonui_sdk_ios.plist index 1b77d2e50d..2ca010fed5 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/commonui_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/commonui_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Mobile SDK. If You make any Contributions (defined below) to the Zendesk CommonUI SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Mobile SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/core_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/core_sdk_ios.plist index b2c511ad4e..c43a544ec2 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/core_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/core_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Mobile SDK. If You make any Contributions (defined below) to the Zendesk Core SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Mobile SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/lottie-ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/lottie-ios.plist index 09c7bf6306..1844e2aa0e 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/lottie-ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/lottie-ios.plist @@ -208,8 +208,6 @@ See the License for the specific language governing permissions and limitations under the License. - License - Apache-2.0 Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/messaging_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/messaging_sdk_ios.plist index 0cc9285932..10db3d5d8d 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/messaging_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/messaging_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Mobile SDK. If You make any Contributions (defined below) to the Zendesk Messaging SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Mobile SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/messagingapi_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/messagingapi_sdk_ios.plist index e90ff2206a..d45fc3a828 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/messagingapi_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/messagingapi_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Mobile SDK. If You make any Contributions (defined below) to the Zendesk MessagingAPI SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Mobile SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/posthog-ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/posthog-ios.plist index f20682aef2..f2e675688a 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/posthog-ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/posthog-ios.plist @@ -30,8 +30,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License - MIT Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/sdkconfigurations_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/sdkconfigurations_sdk_ios.plist index 11eb1b99e2..70ef5da299 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/sdkconfigurations_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/sdkconfigurations_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Mobile SDK. If You make any Contributions (defined below) to the Zendesk SDKConfigurations SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Mobile SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/support_providers_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/support_providers_sdk_ios.plist index 05d7e60598..189913a0eb 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/support_providers_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/support_providers_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Mobile SDK. If You make any Contributions (defined below) to the Zendesk Mobile SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Support Providers SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/support_sdk_ios.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/support_sdk_ios.plist index 8108f7df9f..c6db32305f 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/support_sdk_ios.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/support_sdk_ios.plist @@ -12,8 +12,6 @@ Agreement https://www.zendesk.com/company/customers-partners/#application-develo acknowledge that such terms govern Your use of and access to the Support SDK. If You make any Contributions (defined below) to the Zendesk Support SDK, You hereby grant Zendesk a royalty-free, worldwide, transferable, sub-licensable, irrevocable and perpetual license to incorporate into the Service or the Zendesk API or otherwise use and commercially exploit any Contributions. “Contribution” shall mean any work of authorship, including any modifications or additions to the Mobile SDK or derivative works thereof, that is submitted to Zendesk by You. - License - unknown Type PSGroupSpecifier diff --git a/Resources/Settings.bundle/com.mono0926.LicensePlist/swift-log.plist b/Resources/Settings.bundle/com.mono0926.LicensePlist/swift-log.plist index 9a8234436e..e5b1d702e6 100644 --- a/Resources/Settings.bundle/com.mono0926.LicensePlist/swift-log.plist +++ b/Resources/Settings.bundle/com.mono0926.LicensePlist/swift-log.plist @@ -209,8 +209,6 @@ See the License for the specific language governing permissions and limitations under the License. - License - Apache-2.0 Type PSGroupSpecifier diff --git a/Shared/Extensions/LinearGradient+Planetary.swift b/Shared/Extensions/LinearGradient+Planetary.swift index c480c61819..d59ac84736 100644 --- a/Shared/Extensions/LinearGradient+Planetary.swift +++ b/Shared/Extensions/LinearGradient+Planetary.swift @@ -27,4 +27,10 @@ extension LinearGradient { startPoint: .topLeading, endPoint: .bottomTrailing ) + + public static let cardGradient = LinearGradient( + colors: [Color.cardBgTop, Color.cardBgBottom], + startPoint: .top, + endPoint: .bottom + ) } diff --git a/Source/App/AppController+URL.swift b/Source/App/AppController+URL.swift index f324559dee..d54be605a8 100644 --- a/Source/App/AppController+URL.swift +++ b/Source/App/AppController+URL.swift @@ -123,14 +123,8 @@ extension AppController { } func pushThreadViewController(for identifier: MessageIdentifier) { - Bots.current.thread(rootKey: identifier) { (root, _, error) in - if let root = root { - let controller = ThreadViewController(with: root) - self.push(controller) - } else if let error = error { - self.alert(error: error) - } - } + let controller = MessageViewBuilder.build(identifier: identifier) + self.push(controller, animated: true) } func pushChannelViewController(for hashtag: String) { diff --git a/Source/Bot/Bot+Publish.swift b/Source/Bot/Bot+Publish.swift index 5d33b37e47..41c8ae872c 100644 --- a/Source/Bot/Bot+Publish.swift +++ b/Source/Bot/Bot+Publish.swift @@ -18,23 +18,23 @@ extension Bot { Thread.assertIsMainThread() // publish all images first - self.prepare(images) { - blobs, error in + self.prepare(images) { blobs, error in if Log.optional(error) { completion(Identifier.null, error); return } // mutate post to include blobs let postWithBlobs = post.copy(with: blobs) // publish post - self.publish(content: postWithBlobs) { - postIdentifier, error in + self.publish(content: postWithBlobs) { postIdentifier, error in if Log.optional(error) { completion(.null, error); return } completion(postIdentifier, nil) } } } - - @MainActor func publish(_ post: Post, with images: [UIImage] = []) async throws -> MessageIdentifier { + + @discardableResult + @MainActor + func publish(_ post: Post, with images: [UIImage] = []) async throws -> MessageIdentifier { try await withCheckedThrowingContinuation { continuation in publish(post, with: images) { result, error in if let error = error { @@ -50,23 +50,19 @@ extension Bot { // will quit after first failure and return an error without models // if some of the images were published and later ones fail well // then doing this again will duplicate already published images - func prepare(_ images: [UIImage], - completion: @escaping PublishBlobsCompletion) { + func prepare(_ images: [UIImage], completion: @escaping PublishBlobsCompletion) { Thread.assertIsMainThread() if images.isEmpty { completion([], nil); return } var blobs = [Int: Blob]() - // TODO need to add Bot.publish(blobs) - // TODO check all blobs before publish let datas = images.compactMap { $0.blobData() } - // TODO need Bot error + guard datas.count == images.count else { completion([], nil); return } for (index, data) in datas.enumerated() { let image = images[index] - self.addBlob(data: data) { - identifier, error in + self.addBlob(data: data) { identifier, error in if let error = error { completion([], error); return } let metadata = Blob.Metadata.describing(image, mimeType: .jpeg, data: data) let blob = Blob(identifier: identifier, metadata: metadata) diff --git a/Source/Bot/Bot.swift b/Source/Bot/Bot.swift index 79a401fc9b..08f05eeb4a 100644 --- a/Source/Bot/Bot.swift +++ b/Source/Bot/Bot.swift @@ -248,7 +248,12 @@ protocol Bot: AnyObject, Sendable { /// This is useful for showing all the posts from a particular /// person, like in an About screen. func feed(strategy: FeedStrategy, limit: Int, offset: Int?, completion: @escaping MessagesCompletion) - + + /// Fetches the message with the given Identigfier from the database. + func message(identifier: MessageIdentifier) async throws -> Message? + + func likes(identifier: MessageIdentifier, by author: FeedIdentifier) async throws -> Bool + /// Fetches the post with the given ID from the database. func post(from key: MessageIdentifier) throws -> Message diff --git a/Source/Controller/DirectoryViewController.swift b/Source/Controller/DirectoryViewController.swift index 70ec59805d..7e5a61b0f4 100644 --- a/Source/Controller/DirectoryViewController.swift +++ b/Source/Controller/DirectoryViewController.swift @@ -358,7 +358,8 @@ extension DirectoryViewController: UITableViewDelegate { } navigationController?.pushViewController( - ThreadViewController(with: post, startReplying: false), animated: true + MessageViewBuilder.build(identifier: post.id), + animated: true ) case .network: let identity = self.people[indexPath.row].identity diff --git a/Source/Controller/NewPostViewController.swift b/Source/Controller/NewPostViewController.swift deleted file mode 100644 index 72582cf9bb..0000000000 --- a/Source/Controller/NewPostViewController.swift +++ /dev/null @@ -1,262 +0,0 @@ -// -// NewPostViewController.swift -// FBTT -// -// Created by Christoph on 3/30/19. -// Copyright © 2019 Verse Communications Inc. All rights reserved. -// - -import Foundation -import UIKit -import Logger -import Analytics -import CrashReporting -import Combine - -class NewPostViewController: ContentViewController { - - var didPublish: ((Post) -> Void)? - - private lazy var textView = PostTextEditorView() - - // this view manages it's own height constraints - // checkout ImageGallery.open() and close() - private lazy var galleryView: ImageGalleryView = { - let view = ImageGalleryView(height: 75) - view.delegate = self - view.backgroundColor = .cardBackground - return view - }() - - var images: [UIImage] { - galleryView.images - } - - private lazy var buttonsView: PostButtonsView = { - let view = PostButtonsView() - Layout.addSeparator(toTopOf: view) - view.backgroundColor = .cardBackground - return view - }() - - private let imagePicker = UIImagePicker() - - private let draftStore: DraftStore - private let draftKey: String - private let queue = DispatchQueue.global(qos: .userInitiated) - private var cancellables = [AnyCancellable]() - - // MARK: Lifecycle - - init(images: [UIImage] = []) { - self.draftKey = "com.planetary.ios.draft." + (Bots.current.identity ?? "") - self.draftStore = DraftStore(draftKey: draftKey) - super.init(scrollable: false, title: .newPost) - isKeyboardHandlingEnabled = true - view.backgroundColor = .cardBackground - addActions() - setupDrafts() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - let item = UIBarButtonItem( - image: UIImage.verse.dismiss, - style: .plain, - target: self, - action: #selector(dismissWithoutPost) - ) - item.tintColor = .secondaryAction - item.accessibilityLabel = Localized.done.text - self.navigationItem.leftBarButtonItem = item - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override func willMove(toParent parent: UIViewController?) { - super.willMove(toParent: parent) - self.parent?.presentationController?.delegate = self - } - - override func constrainSubviews() { - super.constrainSubviews() - - Layout.fillTop(of: self.contentView, with: self.textView) - - Layout.fillSouth(of: self.textView, with: self.galleryView) - - Layout.fillBottom(of: self.contentView, with: self.buttonsView, respectSafeArea: false) - self.buttonsView.pinTop(toBottomOf: self.galleryView) - self.buttonsView.constrainHeight(to: PostButtonsView.viewHeight) - } - - // MARK: - Drafts - - /// Configures the view to save and load draft posts from NSUserDefaults. - private func setupDrafts() { - Task { - if let draft = await draftStore.loadDraft() { - if let text = draft.attributedText { - textView.attributedText = text - } - galleryView.add(draft.images) - Log.info("Restored draft") - } - - textView - .textPublisher - .throttle(for: 3, scheduler: queue, latest: true) - .sink { [weak self] newText in - let newTextValue = newText.map { AttributedString($0) } - Task(priority: .userInitiated) { - await self?.draftStore.save(text: newTextValue, images: self?.images ?? []) - } - } - .store(in: &cancellables) - } - } - - // MARK: Actions - - private func addActions() { - self.buttonsView.photoButton.addTarget(self, action: #selector(photoButtonTouchUpInside), for: .touchUpInside) - self.buttonsView.previewToggle.addTarget(self, action: #selector(previewToggled), for: .valueChanged) - self.buttonsView.postButton.action = didPressPostButton - } - - @objc - private func photoButtonTouchUpInside(sender: AnyObject) { - - Analytics.shared.trackDidTapButton(buttonName: "attach_photo") - self.imagePicker.present(from: sender, controller: self) { [weak self] image in - if let image = image { self?.galleryView.add(image) } - self?.imagePicker.dismiss() - } - } - - @objc - private func previewToggled() { - Analytics.shared.trackDidTapButton(buttonName: "preview") - self.textView.previewActive = self.buttonsView.previewToggle.isOn - } - - func didPressPostButton(sender: AnyObject) { - self.lookBusy(message: Localized.NewPost.publishing) - Analytics.shared.trackDidTapButton(buttonName: "post") - self.buttonsView.postButton.isHidden = true - - let hasText = self.textView.attributedText.length > 0 - let hasImages = !self.galleryView.images.isEmpty - - guard hasText || hasImages else { - self.lookReady() - return - } - - let text = self.textView.attributedText - let post = Post(attributedText: text) - let images = self.galleryView.images - - let draftStore = draftStore - let textValue = AttributedString(text) - Task.detached(priority: .userInitiated) { - await draftStore.save(text: textValue, images: self.galleryView.images) - do { - _ = try await Bots.current.publish(post, with: images) - Analytics.shared.trackDidPost(characterCount: post.text.count) - await self.dismiss(didPublish: post) - try StoreReviewController.promptIfConditionsMet() - } catch { - Log.optional(error) - CrashReporting.shared.reportIfNeeded(error: error) - await self.alert(error: error) - } - await self.lookReady() - } - } - - private func dismiss(didPublish post: Post) async { - // Clear UI buffers so they don't get saved as a draft on dismiss - textView.clear() - galleryView.removeAll() - await self.draftStore.clearDraft() - self.didPublish?(post) - self.dismiss(animated: true) - } - - @objc - private func dismissWithoutPost() { - let textValue = AttributedString(self.textView.attributedText) - Task.detached(priority: .userInitiated) { - await self.draftStore.save(text: textValue, images: self.images) - } - self.dismiss(animated: true) - } - - // MARK: Animations - - private func lookBusy(message: Localizable? = nil) { - AppController.shared.showProgress(after: 0.2, statusText: message?.text) - self.buttonsView.photoButton.isEnabled = false - self.buttonsView.postButton.isEnabled = false - self.buttonsView.previewToggle.isEnabled = false - self.buttonsView.postButton.isHidden = true - } - - private func lookReady() { - AppController.shared.hideProgress() - self.buttonsView.photoButton.isEnabled = true - self.buttonsView.postButton.isEnabled = true - self.buttonsView.postButton.isHidden = false - self.buttonsView.previewToggle.isEnabled = true - } -} - -extension NewPostViewController: ImageGalleryViewDelegate { - - // Limits the max number of images to 8 - func imageGalleryViewDidChange(_ view: ImageGalleryView) { - self.buttonsView.photoButton.isEnabled = view.images.count < 8 - view.images.isEmpty ? view.close() : view.open() - let textValue = AttributedString(textView.attributedText) - Task.detached(priority: .userInitiated) { - await self.draftStore.save(text: textValue, images: view.images) - } - } - - func imageGalleryView( - _ view: ImageGalleryView, - didSelect image: UIImage, - at indexPath: IndexPath - ) { - self.confirm( - message: Localized.NewPost.confirmRemove.text, - isDestructive: true, - confirmTitle: Localized.NewPost.remove.text, - confirmClosure: { view.remove(at: indexPath) } - ) - } -} - -extension NewPostViewController: UIAdaptivePresentationControllerDelegate { - - func presentationControllerWillDismiss(_ presentationController: UIPresentationController) { - let hasText = self.textView.attributedText.length > 0 - let hasImages = !self.galleryView.images.isEmpty - - if hasText || hasImages { - let textValue = AttributedString(textView.attributedText) - Task.detached(priority: .userInitiated) { - await self.draftStore.save(text: textValue, images: self.images) - } - } - } -} diff --git a/Source/Controller/NotificationsViewController.swift b/Source/Controller/NotificationsViewController.swift index 1b990e65de..da4c6d365f 100644 --- a/Source/Controller/NotificationsViewController.swift +++ b/Source/Controller/NotificationsViewController.swift @@ -20,17 +20,6 @@ class NotificationsViewController: ContentViewController, HelpDrawerViewControll /// The last time we loaded the reports from the database or we checked if there are new reports to show private var lastTimeNewReportsUpdatesWasChecked = Date() - - private lazy var newPostBarButtonItem: UIBarButtonItem = { - let image = UIImage.navIconWrite - let item = UIBarButtonItem( - image: image, - style: .plain, - target: self, - action: #selector(newPostButtonTouchUpInside) - ) - return item - }() lazy var helpButton: UIBarButtonItem = { HelpDrawerCoordinator.helpBarButton(for: self) }() var helpDrawerType: HelpDrawer { .notifications } @@ -68,7 +57,7 @@ class NotificationsViewController: ContentViewController, HelpDrawerViewControll init() { super.init(scrollable: false, title: .notifications) - navigationItem.rightBarButtonItems = [newPostBarButtonItem, helpButton] + navigationItem.rightBarButtonItems = [helpButton] } required init?(coder aDecoder: NSCoder) { @@ -194,17 +183,6 @@ class NotificationsViewController: ContentViewController, HelpDrawerViewControll func didCreateReport(notification: Notification) { checkForNotificationUpdates(force: true) } - - @objc - func newPostButtonTouchUpInside() { - Analytics.shared.trackDidTapButton(buttonName: "compose") - let controller = NewPostViewController() - controller.didPublish = { [weak self] _ in - self?.load() - } - let navController = UINavigationController(rootViewController: controller) - self.present(navController, animated: true, completion: nil) - } } private class NotificationsTableViewDataSource: MessageTableViewDataSource { @@ -292,7 +270,7 @@ private class NotificationsTableViewDelegate: MessageTableViewDelegate { self.viewController?.navigationController?.pushViewController(controller, animated: true) } else if message.contentType == .post { Analytics.shared.trackDidSelectItem(kindName: "post") - let controller = ThreadViewController(with: message) + let controller = MessageViewBuilder.build(identifier: message.id) self.viewController?.navigationController?.pushViewController(controller, animated: true) } } diff --git a/Source/Controller/StoreReviewController.swift b/Source/Controller/StoreReviewController.swift index 25e7c65071..2a7af06971 100644 --- a/Source/Controller/StoreReviewController.swift +++ b/Source/Controller/StoreReviewController.swift @@ -65,7 +65,7 @@ enum StoreReviewController { guard let identity = Bots.current.identity else { throw PromptError.cannotReviewWhileLoggedOut } - + guard !isYearlyPromptCountExceeded else { return } diff --git a/Source/Controller/ThreadViewController.swift b/Source/Controller/ThreadViewController.swift index 1edcfc086a..d98a9c014c 100644 --- a/Source/Controller/ThreadViewController.swift +++ b/Source/Controller/ThreadViewController.swift @@ -191,10 +191,6 @@ class ThreadViewController: ContentViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) headerView.removeFromSuperview() - let textValue = AttributedString(replyTextView.attributedText) - Task.detached(priority: .userInitiated) { - await self.draftStore.save(text: textValue, images: self.images) - } } private func load(animated: Bool = true, completion: (() -> Void)? = nil) { @@ -212,29 +208,6 @@ class ThreadViewController: ContentViewController { } } } - - private func setUpDrafts() { - Task { - if let draft = await self.draftStore.loadDraft() { - if let text = draft.attributedText { - replyTextView.attributedText = text - } - galleryView.add(draft.images) - Log.info("Restored draft") - } - - replyTextView - .textPublisher - .throttle(for: 3, scheduler: queue, latest: true) - .sink { [weak self] newText in - let newTextValue = newText.map { AttributedString($0) } - Task(priority: .userInitiated) { - await self?.draftStore.save(text: newTextValue, images: self?.images ?? []) - } - } - .store(in: &cancellables) - } - } private func refresh() { self.load() @@ -270,7 +243,6 @@ class ThreadViewController: ContentViewController { self.interactionView.replies = replies as? StaticDataProxy self.interactionView.update() replyTextView.isHidden = root.offChain == true - setUpDrafts() } override func viewDidLayoutSubviews() { @@ -381,7 +353,6 @@ class ThreadViewController: ContentViewController { let draftStore = draftStore let textValue = AttributedString(text) Task.detached(priority: .userInitiated) { - await draftStore.save(text: textValue, images: images) do { let messageID = try await Bots.current.publish(post, with: images) Analytics.shared.trackDidReply(characterCount: post.text.count) @@ -512,10 +483,6 @@ extension ThreadViewController: ImageGalleryViewDelegate { func imageGalleryViewDidChange(_ view: ImageGalleryView) { self.buttonsView.photoButton.isEnabled = view.images.count < 8 view.images.isEmpty ? view.close() : view.open() - let textValue = AttributedString(replyTextView.attributedText) - Task.detached(priority: .userInitiated) { - await self.draftStore.save(text: textValue, images: view.images) - } } func imageGalleryView(_ view: ImageGalleryView, didSelect image: UIImage, at indexPath: IndexPath) { diff --git a/Source/FakeBot/FakeBot.swift b/Source/FakeBot/FakeBot.swift index e177eb1e78..5630371da0 100644 --- a/Source/FakeBot/FakeBot.swift +++ b/Source/FakeBot/FakeBot.swift @@ -328,7 +328,15 @@ class FakeBot: Bot, @unchecked Sendable { func feed(identity: Identity, completion: PaginatedCompletion) { completion(StaticDataProxy(), nil) } - + + func message(identifier: MessageIdentifier) async throws -> Message? { + return nil + } + + func likes(identifier: MessageIdentifier, by author: FeedIdentifier) async throws -> Bool { + return false + } + func post(from key: MessageIdentifier) throws -> Message { throw FakeBotError.runtimeError("not implemented") } diff --git a/Source/Generated/Assets+Planetary.swift b/Source/Generated/Assets+Planetary.swift index 7ae16df8a1..886e4a738b 100644 --- a/Source/Generated/Assets+Planetary.swift +++ b/Source/Generated/Assets+Planetary.swift @@ -210,6 +210,7 @@ extension Image { static let iconTwit = Image("icon-twit", bundle: Bundle.current) static let imageOnboarding = Image("image-onboarding", bundle: Bundle.current) static let launch = Image("launch", bundle: Bundle.current) + static let messageNotVisible = Image("message-not-visible", bundle: Bundle.current) static let missingAboutIcon = Image("missing-about-icon", bundle: Bundle.current) static let navIconCamera = Image("nav-icon-camera", bundle: Bundle.current) static let navIconDismiss = Image("nav-icon-dismiss", bundle: Bundle.current) @@ -280,6 +281,7 @@ extension UIImage { static let iconTwit = UIImage(named: "icon-twit", in: Bundle.current, with: nil)! static let imageOnboarding = UIImage(named: "image-onboarding", in: Bundle.current, with: nil)! static let launch = UIImage(named: "launch", in: Bundle.current, with: nil)! + static let messageNotVisible = UIImage(named: "message-not-visible", in: Bundle.current, with: nil)! static let missingAboutIcon = UIImage(named: "missing-about-icon", in: Bundle.current, with: nil)! static let navIconCamera = UIImage(named: "nav-icon-camera", in: Bundle.current, with: nil)! static let navIconDismiss = UIImage(named: "nav-icon-dismiss", in: Bundle.current, with: nil)! diff --git a/Source/GoBot/FeedStrategy/RepliesStrategy.swift b/Source/GoBot/FeedStrategy/RepliesStrategy.swift new file mode 100644 index 0000000000..bc7fa3ed6e --- /dev/null +++ b/Source/GoBot/FeedStrategy/RepliesStrategy.swift @@ -0,0 +1,198 @@ +// +// RepliesStrategy.swift +// Planetary +// +// Created by Martin Dutra on 12/2/23. +// Copyright © 2023 Verse Communications Inc. All rights reserved. +// + +import Foundation +import SQLite +import Logger + +/// This algorithm returns a feed with replies to a message +final class RepliesStrategy: NSObject, FeedStrategy { + + // swiftlint:disable indentation_width + /// SQL query to count the total number of items in the feed + /// + /// The WHERE clauses are as follows: + /// - Only posts and follows (contacts) + /// - Discard private messages + /// - Discard hidden messages + /// - Only follows (contacts) to people we know something about + /// - Only posts and follows from user itsef + /// - Discard posts and follows from the future + private let countNumberOfKeysQuery = """ + SELECT + COUNT(*) + FROM + tangles t + JOIN messagekeys rmk ON rmk.id = t.root + JOIN messagekeys ON messagekeys.id = t.msg_ref + JOIN messages messages ON messages.msg_id = t.msg_ref + JOIN authors a ON a.id = messages.author_id + WHERE + messages.type IN ('post', 'vote') + AND rmk.key = :message_identifier + AND messages.hidden = FALSE; + """ + // swiftlint:enable indentation_width + + // swiftlint:disable indentation_width + /// SQL query to return the feed's keyvalues + /// + /// The SELECT clauses are as follows: + /// - All data from message, post, contact, tangle, messagekey, author and about of the author + /// - The identified of the followed author if the message is a follow (contact) + /// - A bool column indicating if the message has blobs + /// - A bool column indicating if the message has feed mentions + /// - A bool column indicating if the message has message mentions + /// - The number of replies to the message + /// + /// The WHERE clauses are as follows: + /// - Only posts and follows (contacts) + /// - Discard private messages + /// - Discard hidden messages + /// - Only follows (contacts) to people we know something about + /// - Only posts and follows from user itsef + /// - Discard posts and follows from the future or before the message + /// + /// The result is sorted by date + private let fetchMessagesQuery = """ + SELECT + messages.*, + posts.*, + tangles.*, + messagekeys.*, + authors.*, + abouts.*, + votes.*, + EXISTS ( + SELECT + 1 + FROM + post_blobs + WHERE + post_blobs.msg_ref = messages.msg_id + ) as has_blobs, + EXISTS ( + SELECT + 1 + FROM + mention_feed + WHERE + mention_feed.msg_ref = messages.msg_id + ) as has_feed_mentions, + EXISTS ( + SELECT + 1 + FROM + mention_message + WHERE + mention_message.msg_ref = messages.msg_id + ) as has_message_mentions, + ( + SELECT + COUNT(*) + FROM + tangles + WHERE + root = messages.msg_id + ) as replies_count, + ( + SELECT + GROUP_CONCAT(abouts.image, ';') + FROM + tangles + JOIN messages AS tangled_message ON tangled_message.msg_id = tangles.msg_ref + JOIN abouts ON abouts.about_id = tangled_message.author_id + WHERE + tangles.root = messages.msg_id + AND abouts.image IS NOT NULL + LIMIT + 2 + ) as replies + FROM + tangles t + JOIN messagekeys rmk ON rmk.id = t.root + JOIN messagekeys ON messagekeys.id = t.msg_ref + JOIN messages messages ON messages.msg_id = t.msg_ref + JOIN authors ON authors.id = messages.author_id + LEFT JOIN tangles ON tangles.msg_ref = messages.msg_id + LEFT JOIN abouts ON abouts.about_id = messages.author_id + LEFT JOIN posts ON posts.msg_ref = t.msg_ref + LEFT JOIN votes ON votes.msg_ref = t.msg_ref + WHERE + messages.type IN ('post', 'vote') + AND rmk.key = :message_identifier + AND messages.hidden = FALSE + AND messages.is_decrypted = FALSE + ORDER BY + messages.claimed_at ASC + LIMIT + :limit + OFFSET + :offset; + """ + // swiftlint:enable indentation_width + + let identifier: MessageIdentifier + + override init() { + self.identifier = .null + super.init() + } + + init(identifier: MessageIdentifier) { + self.identifier = identifier + super.init() + } + + required init?(coder: NSCoder) { + self.identifier = .null + super.init() + } + + func encode(with coder: NSCoder) {} + + func countNumberOfKeys(connection: Connection, userId: Int64) throws -> Int { + let query = try connection.prepare(countNumberOfKeysQuery) + + let bindings: [String: Binding?] = [ + ":message_identifier": identifier + ] + + if let count = try query.scalar(bindings) as? Int64 { + return Int(truncatingIfNeeded: count) + } + return 0 + } + + func countNumberOfKeys(connection: Connection, userId: Int64, since message: MessageIdentifier) throws -> Int { + return 0 + } + + func fetchMessages(database: ViewDatabase, userId: Int64, limit: Int, offset: Int?) throws -> [Message] { + guard let connection = try? database.checkoutConnection() else { + Log.error("db is closed") + return [] + } + + let query = try connection.prepare(fetchMessagesQuery) + let bindings: [String: Binding?] = [ + ":message_identifier": identifier, + ":limit": limit, + ":offset": offset ?? 0 + ] + let messages = try query.bind(bindings).prepareRowIterator().map { messageRow -> Message? in + try buildMessage(messageRow: messageRow, database: database) + } + let compactMessages = messages.compactMap { $0 } + return compactMessages + } + + private func buildMessage(messageRow: Row, database: ViewDatabase) throws -> Message? { + try Message(row: messageRow, database: database) + } +} diff --git a/Source/GoBot/GoBot.swift b/Source/GoBot/GoBot.swift index 71bd0bf7a4..68e7517476 100644 --- a/Source/GoBot/GoBot.swift +++ b/Source/GoBot/GoBot.swift @@ -1477,7 +1477,33 @@ class GoBot: Bot, @unchecked Sendable { func feed(identity: Identity, completion: @escaping PaginatedCompletion) { feed(strategy: NoHopFeedAlgorithm(identity: identity), completion: completion) } - + + func message(identifier: MessageIdentifier) async throws -> Message? { + try await withCheckedThrowingContinuation { continuation in + userInitiatedQueue.async { [identifier] in + do { + let message = try self.database.message(with: identifier) + continuation.resume(returning: message) + } catch { + continuation.resume(throwing: error) + } + } + } + } + + func likes(identifier: MessageIdentifier, by author: FeedIdentifier) async throws -> Bool { + try await withCheckedThrowingContinuation { continuation in + userInitiatedQueue.async { [identifier] in + do { + let liked = try self.database.likesMessage(with: identifier, author: author) + continuation.resume(returning: liked) + } catch { + continuation.resume(throwing: error) + } + } + } + } + func post(from key: MessageIdentifier) throws -> Message { try self.database.post(with: key) } diff --git a/Source/GoBot/ViewDatabase.swift b/Source/GoBot/ViewDatabase.swift index 56c5220885..7f9220ecf3 100644 --- a/Source/GoBot/ViewDatabase.swift +++ b/Source/GoBot/ViewDatabase.swift @@ -703,9 +703,127 @@ class ViewDatabase { } } - func message(with id: MessageIdentifier) throws -> Message { - let msgId = try self.msgID(of: id, make: false) - return try post(with: msgId) + func message(with identifier: MessageIdentifier) throws -> Message? { + let db = try checkoutConnection() + + // swiftlint:disable indentation_width + let query = """ + SELECT + messages.*, + posts.*, + contacts.*, + contact_about.about_id, + tangles.*, + messagekeys.*, + votes.*, + authors.*, + author_about.*, + contact_author.author AS contact_identifier, + EXISTS ( + SELECT + 1 + FROM + post_blobs + WHERE + post_blobs.msg_ref = messages.msg_id + ) as has_blobs, + EXISTS ( + SELECT + 1 + FROM + mention_feed + WHERE + mention_feed.msg_ref = messages.msg_id + ) as has_feed_mentions, + EXISTS ( + SELECT + 1 + FROM + mention_message + WHERE + mention_message.msg_ref = messages.msg_id + ) as has_message_mentions, + ( + SELECT + COUNT(*) + FROM + tangles + WHERE + root = messages.msg_id + ) as replies_count, + ( + SELECT + GROUP_CONCAT(abouts.image, ';') + FROM + tangles + JOIN messages AS tangled_message ON tangled_message.msg_id = tangles.msg_ref + JOIN abouts ON abouts.about_id = tangled_message.author_id + WHERE + tangles.root = messages.msg_id + AND abouts.image IS NOT NULL + LIMIT + 2 + ) as replies + FROM + messages + LEFT JOIN posts ON messages.msg_id = posts.msg_ref + LEFT JOIN contacts ON messages.msg_id = contacts.msg_ref + LEFT JOIN tangles ON tangles.msg_ref = messages.msg_id + LEFT JOIN votes ON votes.msg_ref = messages.msg_id + JOIN messagekeys ON messagekeys.id = messages.msg_id + JOIN authors ON authors.id = messages.author_id + LEFT JOIN abouts AS author_about ON author_about.about_id = messages.author_id + LEFT JOIN authors AS contact_author ON contact_author.id = contacts.contact_id + LEFT JOIN abouts AS contact_about ON contact_about.about_id = contacts.contact_id + WHERE + messagekeys.key = :message_identifier + LIMIT + 1 + """ + // swiftlint:enable indentation_width + + let bindings: [String: Binding?] = [":message_identifier": identifier] + if let row = try db.prepare(query).bind(bindings).prepareRowIterator().next() { + return try Message(row: row, database: self) + } else { + return nil + } + } + + func likesMessage(with identifier: MessageIdentifier, author: FeedIdentifier) throws -> Bool { + let db = try checkoutConnection() + + // swiftlint:disable indentation_width + let query = """ + SELECT + COUNT(*) > 0 + FROM + tangles t + JOIN messagekeys rmk ON rmk.id = t.root + JOIN messages ON messages.msg_id = t.msg_ref + JOIN authors ON authors.id = messages.author_id + JOIN votes ON votes.msg_ref = t.msg_ref + WHERE + messages.type = 'vote' + AND rmk.key = :message_identifier + AND authors.author = :author_identifier + AND messages.hidden = FALSE + AND messages.is_decrypted = FALSE + AND votes.value > 0 + LIMIT + 1 + """ + // swiftlint:enable indentation_width + + let bindings: [String: Binding?] = [ + ":message_identifier": identifier, + ":author_identifier": author + ] + if let liked = try db.prepare(query).scalar(bindings) as? Int64 { + return liked > 0 + } else { + return false + } } // MARK: pubs & rooms diff --git a/Source/Localization/Localized.swift b/Source/Localization/Localized.swift index a8347c2c5b..ef2051cd57 100644 --- a/Source/Localization/Localized.swift +++ b/Source/Localization/Localized.swift @@ -81,6 +81,7 @@ enum Localized: String, Localizable, CaseIterable { case posted = "{{somebody}} posted" case replied = "{{somebody}} replied" case liked = "{{somebody}} liked" + case reacted = "{{somebody}} reacted" case startedFollowing = "{{somebody}} started following" case stoppedFollowing = "{{somebody}} stopped following" case startedBlocking = "{{somebody}} blocked" @@ -102,6 +103,7 @@ enum Localized: String, Localizable, CaseIterable { case postAction = "Post" case preview = "Preview" case newPost = "New Post" + case newReply = "New Reply" case deletePost = "Delete this post" case editPost = "Edit this post" case confirmDeletePost = "Are you sure you want to delete this post?" @@ -271,6 +273,7 @@ extension Localized { case remove = "Remove" case publishing = "Publishing..." case restoring = "Restoring..." + case mention = "Mention" } } @@ -448,6 +451,11 @@ extension Localized { extension Localized { enum Message: String, Localizable, CaseIterable { + case message = "Message" + case reaction = "Reaction" + case contact = "Contact" + case post = "Post" + case reply = "Reply" case noPostsTitle = "No posts here yet" case noPostsDescription = "This means the user hasn't posted anything, or you don't have enough connections in common to synchronize their posts.\n\nLearn [how gossipping works]({{ link }}) on Planetary." case noPostsInHashtagDescription = "This means no messages have been posted under this hashtag, or you don't have enough connections to synchronize these posts.\n\nLearn [how gossipping works]({{ link }}) on Planetary." @@ -611,6 +619,7 @@ extension Localized { extension Localized { enum Post: String, Localizable, CaseIterable { + case title = "Post" case one = "post" case many = "posts" } diff --git a/Source/Localization/en.lproj/Generated.strings b/Source/Localization/en.lproj/Generated.strings index 377769197f..65aa686d5d 100644 --- a/Source/Localization/en.lproj/Generated.strings +++ b/Source/Localization/en.lproj/Generated.strings @@ -34,6 +34,7 @@ "Localized.posted" = "{{somebody}} posted"; "Localized.replied" = "{{somebody}} replied"; "Localized.liked" = "{{somebody}} liked"; +"Localized.reacted" = "{{somebody}} reacted"; "Localized.startedFollowing" = "{{somebody}} started following"; "Localized.stoppedFollowing" = "{{somebody}} stopped following"; "Localized.startedBlocking" = "{{somebody}} blocked"; @@ -52,6 +53,7 @@ "Localized.postAction" = "Post"; "Localized.preview" = "Preview"; "Localized.newPost" = "New Post"; +"Localized.newReply" = "New Reply"; "Localized.deletePost" = "Delete this post"; "Localized.editPost" = "Edit this post"; "Localized.confirmDeletePost" = "Are you sure you want to delete this post?"; @@ -173,6 +175,7 @@ "NewPost.remove" = "Remove"; "NewPost.publishing" = "Publishing..."; "NewPost.restoring" = "Restoring..."; +"NewPost.mention" = "Mention"; "Offboarding.reset" = "Delete"; "Offboarding.resetIdentity" = "Delete My Identity"; @@ -280,6 +283,11 @@ "ManageRelays.joinPlanetaryRoom" = "Join Planetary Room"; "ManageRelays.joinPlanetaryRoomDescription" = "Joining the official Planetary room server will allow to register aliases like yourname.planetary.name, and sync directly with others in the room."; +"Message.message" = "Message"; +"Message.reaction" = "Reaction"; +"Message.contact" = "Contact"; +"Message.post" = "Post"; +"Message.reply" = "Reply"; "Message.noPostsTitle" = "No posts here yet"; "Message.noPostsDescription" = "This means the user hasn't posted anything, or you don't have enough connections in common to synchronize their posts.\n\nLearn [how gossipping works]({{ link }}) on Planetary."; "Message.noPostsInHashtagDescription" = "This means no messages have been posted under this hashtag, or you don't have enough connections to synchronize these posts.\n\nLearn [how gossipping works]({{ link }}) on Planetary."; @@ -356,6 +364,7 @@ "Channel.one" = "channel"; "Channel.many" = "channels"; +"Post.title" = "Post"; "Post.one" = "post"; "Post.many" = "posts"; diff --git a/Source/Localization/es-UY.lproj/Generated.strings b/Source/Localization/es-UY.lproj/Generated.strings index f5de9d86f8..4d0b5181d3 100644 --- a/Source/Localization/es-UY.lproj/Generated.strings +++ b/Source/Localization/es-UY.lproj/Generated.strings @@ -34,6 +34,7 @@ "Localized.posted" = "{{somebody}} posteó"; "Localized.replied" = "{{somebody}} contestó"; "Localized.liked" = "A {{somebody}} le gustó"; +"Localized.reacted" = "{{somebody}} reaccionó"; "Localized.startedFollowing" = "{{somebody}} siguió a"; "Localized.stoppedFollowing" = "{{somebody}} dejó de seguir a"; "Localized.startedBlocking" = "{{somebody}} ignoró a"; @@ -52,6 +53,7 @@ "Localized.postAction" = "Postear"; "Localized.preview" = "Vista previa"; "Localized.newPost" = "Nuevo post"; +"Localized.newReply" = "Nueva respuesta"; "Localized.deletePost" = "Eliminar este post"; "Localized.editPost" = "Editar este post"; "Localized.confirmDeletePost" = "¿Estás seguro de que querés borrar este post?"; @@ -173,6 +175,7 @@ "NewPost.remove" = "Eliminar"; "NewPost.publishing" = "Publicando..."; "NewPost.restoring" = "Restaurando..."; +"NewPost.mention" = "Mencionar"; "Offboarding.reset" = "Eliminar"; "Offboarding.resetIdentity" = "Eliminar mi identidad"; @@ -280,6 +283,11 @@ "ManageRelays.joinPlanetaryRoom" = "Unirse a la sala Planetary"; "ManageRelays.joinPlanetaryRoomDescription" = "Al unirte al servidor de la sala oficial de Planetary podrás registrar alias como tunombre.planetary.name y sincronizar directamente con otros en la sala."; +"Message.message" = "Mensaje"; +"Message.reaction" = "Reacción"; +"Message.contact" = "Contacto"; +"Message.post" = "Publicación"; +"Message.reply" = "Respuesta"; "Message.noPostsTitle" = "Aún no hay posts aquí"; "Message.noPostsDescription" = "Esto significa que el usuario no ha posteado nada o no tienes suficientes conexiones en común para sincronizarte con sus posts.\n\nAprendé [cómo se chusmea]({{ link }}) en Planetary."; "Message.noPostsInHashtagDescription" = "Esto significa que no se han publicado posts con este hashtag, o no tienes las suficientes conexiones como para sincronizarte con estos posts.\n\nAprende [cómo se chusmea]({{ link }}) en Planetary."; @@ -356,6 +364,7 @@ "Channel.one" = "hashtag"; "Channel.many" = "hashtags"; +"Post.title" = "Publicación"; "Post.one" = "publicación"; "Post.many" = "publicaciones"; diff --git a/Source/Localization/es.lproj/Generated.strings b/Source/Localization/es.lproj/Generated.strings index 55df46ff08..e425b242f0 100644 --- a/Source/Localization/es.lproj/Generated.strings +++ b/Source/Localization/es.lproj/Generated.strings @@ -34,6 +34,7 @@ "Localized.posted" = "{{somebody}} publicó"; "Localized.replied" = "{{somebody}} respondió"; "Localized.liked" = "A {{somebody}} le gustó"; +"Localized.reacted" = "{{somebody}} reaccionó"; "Localized.startedFollowing" = "{{somebody}} siguió a"; "Localized.stoppedFollowing" = "{{somebody}} dejó de seguir a"; "Localized.startedBlocking" = "{{somebody}} ignoró a"; @@ -52,6 +53,7 @@ "Localized.postAction" = "Publicar"; "Localized.preview" = "Vista previa"; "Localized.newPost" = "Nueva publicación"; +"Localized.newReply" = "Nueva respuesta"; "Localized.deletePost" = "Eliminar esta publicación"; "Localized.editPost" = "Editar esta publicación"; "Localized.confirmDeletePost" = "¿Estás seguro de que quieres eliminar esta publicación?"; @@ -173,6 +175,7 @@ "NewPost.remove" = "Quitar"; "NewPost.publishing" = "Publicando..."; "NewPost.restoring" = "Restaurando..."; +"NewPost.mention" = "Mencionar"; "Offboarding.reset" = "Eliminar"; "Offboarding.resetIdentity" = "Eliminar mi identidad"; @@ -280,6 +283,11 @@ "ManageRelays.joinPlanetaryRoom" = "Unirse a la sala Planetary"; "ManageRelays.joinPlanetaryRoomDescription" = "Al unirte al servidor de la sala oficial de Planetary podrás registrar alias como tunombre.planetary.name y sincronizar directamente con otros en la sala."; +"Message.message" = "Mensaje"; +"Message.reaction" = "Reacción"; +"Message.contact" = "Contacto"; +"Message.post" = "Publicación"; +"Message.reply" = "Respuesta"; "Message.noPostsTitle" = "No hay publicaciones aquí todavía"; "Message.noPostsDescription" = "Esto significa que el usuario no ha publicado nada o no tienes suficientes conexiones en común para sincronizarte con sus publicaciones.\n\nAprende [cómo funciona susurrar]({{ link }}) en Planetary."; "Message.noPostsInHashtagDescription" = "Esto significa que no se han publicado mensajes con este hashtag, o no tienes las suficientes conexiones como para sincronizarte con estas publicaciones.\n\nAprende [cómo funciona susurrar]({{ link }}) en Planetary."; @@ -356,6 +364,7 @@ "Channel.one" = "etiqueta"; "Channel.many" = "etiquetas"; +"Post.title" = "Publicación"; "Post.one" = "publicación"; "Post.many" = "publicaciones"; diff --git a/Source/Model/Draft.swift b/Source/Model/Draft.swift index 2f48c93c2b..f1db8e864b 100644 --- a/Source/Model/Draft.swift +++ b/Source/Model/Draft.swift @@ -16,24 +16,24 @@ extension AttributedString: @unchecked Sendable {} #endif /// A class representing a drafted post. Supports NSCoding so it can be saved to disk. -class Draft: NSObject, NSCoding { +final class Draft: NSObject, NSCoding, Sendable { - var attributedText: NSAttributedString? - var images: [UIImage] = [] + let text: String + let images: [UIImage] - init(attributedText: NSAttributedString?, images: [UIImage]) { - self.attributedText = attributedText + init(text: String, images: [UIImage]) { + self.text = text self.images = images } // swiftlint:disable legacy_objc_type required init?(coder: NSCoder) { - self.attributedText = coder.decodeObject(of: NSAttributedString.self, forKey: "attributedText") + self.text = coder.decodeObject(forKey: "text") as? String ?? "" self.images = (coder.decodeObject(of: NSArray.self, forKey: "images") as? [UIImage]) ?? [] } func encode(with coder: NSCoder) { - coder.encode(attributedText, forKey: "attributedText") + coder.encode(text, forKey: "text") coder.encode(images, forKey: "images") } // swiftlint:enable legacy_objc_type @@ -43,7 +43,7 @@ class Draft: NSObject, NSCoding { return false } - return otherDraft.attributedText == attributedText && otherDraft.images == images + return otherDraft.text == text && otherDraft.images == images } } @@ -67,10 +67,10 @@ actor DraftStore { return nil } - - func save(text string: AttributedString?, images: [UIImage]) { + + func save(text string: String, images: [UIImage]) { let draft = Draft( - attributedText: string.map { NSAttributedString($0) }, + text: string, images: images ) diff --git a/Source/Model/Message.swift b/Source/Model/Message.swift index 90c0438c2b..50a141043e 100644 --- a/Source/Model/Message.swift +++ b/Source/Model/Message.swift @@ -95,7 +95,7 @@ struct Message: Codable, Identifiable, @unchecked Sendable { extension Message: Equatable { static func == (lhs: Message, rhs: Message) -> Bool { - lhs.key == rhs.key + lhs.key == rhs.key && lhs.metadata.replies.count == rhs.metadata.replies.count } } diff --git a/Source/Model/Notification+Post.swift b/Source/Model/Notification+Post.swift index 7e2c2bfb26..3fc1b00337 100644 --- a/Source/Model/Notification+Post.swift +++ b/Source/Model/Notification+Post.swift @@ -10,6 +10,7 @@ import Foundation extension Notification.Name { static let didPublishPost = Notification.Name("didPublishPost") + static let didPublishVote = Notification.Name("didPublishVote") } extension Notification { @@ -18,6 +19,10 @@ extension Notification { self.userInfo?["post"] as? Post } + var identifier: MessageIdentifier? { + self.userInfo?["identifier"] as? MessageIdentifier + } + static func didPublishPost(_ post: Post) -> Notification { Notification( name: .didPublishPost, @@ -25,4 +30,12 @@ extension Notification { userInfo: ["post": post] ) } + + static func didPublishVote(to message: MessageIdentifier) -> Notification { + Notification( + name: .didPublishVote, + object: nil, + userInfo: ["identifier": message] + ) + } } diff --git a/Source/Model/Post.swift b/Source/Model/Post.swift index eb50c4df44..97bcaf206f 100644 --- a/Source/Model/Post.swift +++ b/Source/Model/Post.swift @@ -7,7 +7,7 @@ import Foundation -class Post: ContentCodable { +final class Post: ContentCodable, Sendable { enum CodingKeys: String, CodingKey { case branch @@ -37,18 +37,19 @@ class Post: ContentCodable { /// Intended to be used when publishing a new Post from a UI. /// Check out NewPostViewController for an example. - init(attributedText: NSAttributedString, - root: MessageIdentifier? = nil, - branches: [MessageIdentifier]? = nil) { + init( + attributedText: NSAttributedString, + root: MessageIdentifier? = nil, + branches: [MessageIdentifier]? = nil + ) { // required self.branch = branches self.root = root self.text = attributedText.markdown self.type = .post - var mentionsFromHashtags = attributedText.string.hashtags().map { - tag in - Mention(link: tag.string) + var mentionsFromHashtags = attributedText.string.hashtags().map { hashtag in + Mention(link: hashtag.string) } mentionsFromHashtags.append(contentsOf: attributedText.mentions()) @@ -59,35 +60,65 @@ class Post: ContentCodable { self.reply = nil } + /// Intended to be used when publishing a new Post from a UI. + /// Check out PreviewView for an example. + init(text: String, root: MessageIdentifier? = nil, branches: [MessageIdentifier]? = nil) { + self.branch = branches + self.root = root + self.text = text + self.type = .post + + let attributed = text.parseMarkdown() + var mentions = [Mention]() + for attributedRun in attributed.runs { + if let link = attributedRun.attributes.link ?? attributedRun.attributes.imageURL { + let name = String(attributed[attributedRun.range].characters) + if link.scheme == URL.planetaryScheme { + let path = String(link.path.dropFirst()) + if path.isValidIdentifier || path.isHashtag { + mentions.append(Mention(link: path, name: name)) + } + } + } + } + self.mentions = mentions.isEmpty ? nil : mentions + + // unused + self.recps = nil + self.reply = nil + } + /// Intended to be used to create models in the view database or unit tests. - init(blobs: Blobs? = nil, - branches: [MessageIdentifier]? = nil, - hashtags: Hashtags? = nil, - mentions: [Mention]? = nil, - root: MessageIdentifier? = nil, - text: String) { + init( + blobs: Blobs? = nil, + branches: [MessageIdentifier]? = nil, + hashtags: Hashtags? = nil, + mentions: [Mention]? = nil, + root: MessageIdentifier? = nil, + text: String + ) { // required self.branch = branches self.root = root self.text = text self.type = .post - var m: Mentions = [] + var mention: Mentions = [] if let mentions = mentions { - m = mentions + mention = mentions } if let blobs = blobs { - for b in blobs { - m.append(b.asMention()) + for blob in blobs { + mention.append(blob.asMention()) } } if let tags = hashtags { - for h in tags { - m.append(Mention(link: h.string)) + for hashtag in tags { + mention.append(Mention(link: hashtag.string)) } } // keep it optional - self.mentions = m.count > 0 ? m : nil + self.mentions = mention.count > 0 ? mention : nil // unused self.recps = nil @@ -137,38 +168,36 @@ extension Post { } } -/* code to handle both kinds of recpients: - patchcore publishes this object instead of just the key as a string - { link: @pubkey, name: somenick} - - - handling from https://stackoverflow.com/a/49023027 -*/ - enum RecipientElement: Codable { case namedKey(RecipientNamedKey) case string(Identity) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - if let x = try? container.decode(Identity.self) { - self = .string(x) + if let identity = try? container.decode(Identity.self) { + self = .string(identity) return } - if let x = try? container.decode(RecipientNamedKey.self) { - self = .namedKey(x) + if let namedKey = try? container.decode(RecipientNamedKey.self) { + self = .namedKey(namedKey) return } - throw DecodingError.typeMismatch(RecipientElement.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for RecipientElement")) + throw DecodingError.typeMismatch( + RecipientElement.self, + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Wrong type for RecipientElement" + ) + ) } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { - case .namedKey(let x): - try container.encode(x.link) - case .string(let x): - try container.encode(x) + case .namedKey(let namedKey): + try container.encode(namedKey.link) + case .string(let identity): + try container.encode(identity) } } } @@ -183,27 +212,14 @@ struct RecipientNamedKey: Codable { } } -/* TODO: there is a cleaner solution here - tried this to get [Identity] but got the following error so I added getRecipientIdentities as a workaround - Constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause - - - typealias Recipients = [RecipientElement] - - extension Recipients { - func recipients() -> [Identity] { - return getRecipientIdentities(self) - } - } -*/ func getRecipientIdentities(recps: [RecipientElement]) -> [Identity] { var identities: [Identity] = [] - for r in recps { - switch r { - case .string(let str): - identities.append(str) - case .namedKey(let nk): - identities.append(nk.link) + for recipient in recps { + switch recipient { + case .string(let identity): + identities.append(identity) + case .namedKey(let namedKey): + identities.append(namedKey.link) } } return identities diff --git a/Source/Model/SearchResults.swift b/Source/Model/SearchResults.swift index 9f3d56f332..cfb32f7046 100644 --- a/Source/Model/SearchResults.swift +++ b/Source/Model/SearchResults.swift @@ -113,3 +113,20 @@ extension Either: Identifiable, Equatable, Hashable where Left == FeedIdentifier hasher.combine(id) } } + +extension Either where Left == MessageIdentifier, Right == Message { + var id: MessageIdentifier { + switch self { + case .left(let identifier): + return identifier + case .right(let message): + return message.id + } + } + static func == (lhs: Either, rhs: Either) -> Bool { + lhs.id == rhs.id + } + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/Source/UI/Buttons/PillButtonStyle.swift b/Source/UI/Buttons/PillButtonStyle.swift index 81a3d6274a..c24ff50680 100644 --- a/Source/UI/Buttons/PillButtonStyle.swift +++ b/Source/UI/Buttons/PillButtonStyle.swift @@ -9,11 +9,13 @@ import SwiftUI struct PillButtonStyle: ButtonStyle { + var padding: CGFloat = 15 + @SwiftUI.Environment(\.isEnabled) private var isEnabled func makeBody(configuration: Configuration) -> some View { configuration.label - .padding() + .padding(padding) .background( !isEnabled ? Color.pillButtonBackgroundDisabled : configuration.isPressed ? Color.pillButtonBackgroundPressed : Color.pillButtonBackground diff --git a/Source/UI/Cards/LoadingCard.swift b/Source/UI/Cards/LoadingCard.swift new file mode 100644 index 0000000000..526cd5f1d4 --- /dev/null +++ b/Source/UI/Cards/LoadingCard.swift @@ -0,0 +1,33 @@ +// +// LoadingCard.swift +// Planetary +// +// Created by Martin Dutra on 25/3/23. +// Copyright © 2023 Verse Communications Inc. All rights reserved. +// + +import SwiftUI + +struct LoadingCard: View { + + var style: CardStyle + + var body: some View { + VStack(spacing: 0) { + PeerConnectionAnimationView(peerCount: 3, color: UIColor.secondaryTxt) + .frame(maxWidth: .infinity) + .frame(height: 150) + } + .background( + LinearGradient.cardGradient + ) + .cornerRadius(20) + .padding(EdgeInsets(top: 15, leading: 15, bottom: 0, trailing: 15)) + } +} + +struct LoadingCard_Previews: PreviewProvider { + static var previews: some View { + LoadingCard(style: .compact) + } +} diff --git a/Source/UI/Compose/AttachedImageButton.swift b/Source/UI/Compose/AttachedImageButton.swift new file mode 100644 index 0000000000..7c276f2f51 --- /dev/null +++ b/Source/UI/Compose/AttachedImageButton.swift @@ -0,0 +1,62 @@ +// +// AttachedImageButton.swift +// Planetary +// +// Created by Martin Dutra on 14/3/23. +// Copyright © 2023 Verse Communications Inc. All rights reserved. +// + +import SwiftUI + +struct AttachedImageButton: View { + + var image: UIImage + + var onCompletion: ((UIImage) -> Void) + + @State + private var showDeleteAttachmentConfirmation = false + + var body: some View { + Button { + showDeleteAttachmentConfirmation = true + } label: { + ZStack { + Image(uiImage: image) + .resizable() + .frame(width: 65, height: 65) + Image.navIconDismiss.padding(3).background(Circle().foregroundColor(.primaryTxt.opacity(0.4))) + } + } + .confirmationDialog( + Localized.NewPost.remove.text, + isPresented: $showDeleteAttachmentConfirmation, + actions: { + Button(Localized.ok.text, role: .destructive) { + showDeleteAttachmentConfirmation = false + onCompletion(image) + } + Button(Localized.cancel.text, role: .cancel) { + showDeleteAttachmentConfirmation = false + } + }, + message: { + Localized.NewPost.confirmRemove.view + } + ) + } +} + +struct AttachedImageButton_Previews: PreviewProvider { + static var previews: some View { + Group { + AttachedImageButton(image: UIImage(named: "avatar1") ?? .gobotIcon) { image in + print(image) + } + AttachedImageButton(image: UIImage(named: "avatar1") ?? .gobotIcon) { image in + print(image) + } + .preferredColorScheme(.dark) + } + } +} diff --git a/Source/UI/Compose/ComposeView.swift b/Source/UI/Compose/ComposeView.swift new file mode 100644 index 0000000000..88bc77a24f --- /dev/null +++ b/Source/UI/Compose/ComposeView.swift @@ -0,0 +1,321 @@ +// +// ComposeView.swift +// Planetary +// +// Created by Martin Dutra on 9/3/23. +// Copyright © 2023 Verse Communications Inc. All rights reserved. +// + +import Analytics +import Combine +import CrashReporting +import Logger +import PhotosUI +import SwiftUI + +struct ComposeView: View { + + /// Binding used to dimiss this view + @Binding + var isPresenting: Bool + + var root: Message? + + /// State holding the text the user is typing + @StateObject + private var textEditorObserver = TextEditorObserver() + + /// State containing the very last state before `text` changes + /// + /// We need this so that we can compare and decide what has changed. + @State + private var oldText: String = "" + + /// State containing the photos the user is attaching + @State + private var photos: [UIImage] = [] + + /// State containing the offset (index) of text when the user is mentioning someone + /// + /// When we detect the user typed a '@', we save the position of that character here and open a screen + /// that lets the user select someone to mention, then we can replace this character with the full mention. + @State + private var mentionOffset: Int? + + /// State used to present or hide a confirmation dialog that lets the user remove an attached photo. + @State + private var showDeleteAttachmentConfirmation = false + + private var showAvailableMentions: Binding { + Binding { + mentionOffset != nil + } set: { _ in + mentionOffset = nil + } + } + + /// List containing possible identities the user can mention. + @State + private var followings: [Identity]? + + /// If true, we already attempted to load a draft from disk + /// + /// We need this because the `task` modifier can be invoked multiple times if the user goes back from Preview. + @State + private var draftLoaded = false + + @EnvironmentObject + private var botRepository: BotRepository + + /// Used to maintain focus on the text editor when the keyboard is dismissed or the screen appears + @FocusState + private var textEditorIsFocused: Bool + + private var isReply: Bool { + root != nil + } + + var body: some View { + NavigationView { + VStack { + TextEditor(text: $textEditorObserver.text) + .focused($textEditorIsFocused) + .padding() + .scrollContentBackground(.hidden) + .foregroundColor(.primaryTxt) + .onChange(of: textEditorObserver.text) { newValue in + let difference = newValue.difference(from: oldText) + guard difference.count == 1, let change = difference.first else { + oldText = newValue + return + } + switch change { + case .insert(let offset, let element, _): + if element == "@", followings != nil { + mentionOffset = offset + } + default: + break + } + oldText = newValue + } + .sheet(isPresented: showAvailableMentions) { + NavigationStack { + IdentityListView(identities: followings ?? []) { identity in + Task.detached(priority: .userInitiated) { + guard let offset = await mentionOffset else { + return + } + let textToModify = await textEditorObserver.text + let link: String + do { + let about = try await botRepository.current.about(identity: identity) + if let name = about?.name { + link = "[\(name)](\(identity))" + } else { + link = "[\(identity)](\(identity))" + } + } catch { + link = "[\(identity)](\(identity))" + } + var modifiedString = String() + for (i, char) in textToModify.enumerated() { + modifiedString += (i == offset) ? "\(link) " : String(char) + } + await MainActor.run { [modifiedString] in + textEditorObserver.text = modifiedString + oldText = modifiedString + mentionOffset = nil + } + } + } + .navigationTitle(Localized.NewPost.mention.text) + } + .presentationDetents([.medium, .large]) + } + VStack(spacing: 0) { + if !photos.isEmpty { + ScrollView(.horizontal) { + HStack(spacing: 5) { + ForEach(photos, id: \.self) { photo in + AttachedImageButton(image: photo) { image in + guard let index = photos.firstIndex(where: { $0 === image }) else { + return + } + photos.remove(at: index) + saveDraft() + } + } + } + .padding(5) + } + } + Divider().overlay(Color.cardDivider).shadow(color: .cardDividerShadow, radius: 0, x: 0, y: 1) + HStack { + ImagePickerButton { image in + photos.append(image) + saveDraft() + } label: { + Image.iconLibrary + } + .padding(10) + Spacer() + NavigationLink { + PreviewView( + text: textEditorObserver.text, + photos: photos, + root: root, + isPresenting: $isPresenting + ) + .task { + saveDraft() + } + } label: { + Text(Localized.preview.text) + .font(.subheadline) + .foregroundColor(.white) + .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) + .background( + Rectangle() + .fill(LinearGradient.horizontalAccent) + .cornerRadius(17) + ) + } + .padding(10) + } + Color.cardBgBottom.ignoresSafeArea(.all, edges: .bottom).frame(height: 0) + } + .background { + LinearGradient.cardGradient + } + } + .background { + Color.appBg + } + .navigationTitle(isReply ? Localized.newReply.text : Localized.newPost.text) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { + saveDraft() + isPresenting = false + } label: { + Image.navIconDismiss + } + } + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in + textEditorIsFocused = true + } + .onAppear { + textEditorIsFocused = true + } + .onReceive(textEditorObserver.$throttledText) { _ in + saveDraft() + } + .onReceive(NotificationCenter.default.publisher(for: .didPublishPost)) { _ in + clearDraft() + } + .task { + if !draftLoaded { + loadDraft() + draftLoaded = true + } + if followings == nil { + loadFollowings() + } + } + } + .background(Color.navigationbarBg) + } + + private func loadDraft() { + Task.detached(priority: .userInitiated) { + let bot = await botRepository.current + let draftStore = await buildDraftStore(from: bot) + if let draft = await draftStore.loadDraft() { + await MainActor.run { + textEditorObserver.text = draft.text + photos = draft.images + } + Log.info("Restored draft") + } + } + } + + private func saveDraft() { + Task.detached(priority: .userInitiated) { + let bot = await botRepository.current + let draftStore = await buildDraftStore(from: bot) + let currentText = await textEditorObserver.text + let currentPhotos = await photos + await draftStore.save(text: currentText, images: currentPhotos) + Log.debug("Draft saved with \(currentText.count) characters and \(currentPhotos.count) photos.") + } + } + + private func clearDraft() { + Task.detached(priority: .userInitiated) { + let bot = await botRepository.current + let draftStore = await buildDraftStore(from: bot) + await draftStore.clearDraft() + Log.debug("Draft cleared") + } + } + + private func buildDraftStore(from bot: Bot) -> DraftStore { + let currentIdentity = bot.identity ?? "" + let draftKey = "com.planetary.ios.draft." + currentIdentity + return DraftStore(draftKey: draftKey) + } + + private func loadFollowings() { + Task.detached { + let bot = await botRepository.current + guard let currentIdentity = bot.identity else { + return + } + do { + let result: [Identity] = try await bot.followings(identity: currentIdentity) + await MainActor.run { + followings = result + } + } catch { + Log.optional(error) + CrashReporting.shared.reportIfNeeded(error: error) + } + } + } +} + +@MainActor +fileprivate class TextEditorObserver: ObservableObject { + @Published + var throttledText = "" + + @Published + var text = "" + + private var subscriptions = Set() + + init() { + $text + .removeDuplicates() + .throttle(for: .seconds(2), scheduler: RunLoop.main, latest: true) + .sink { [weak self] value in + self?.throttledText = value + } + .store(in: &subscriptions) + } +} + +struct ComposeView_Previews: PreviewProvider { + @State + static var isPresenting = false + + static var previews: some View { + ComposeView(isPresenting: $isPresenting) + .preferredColorScheme(.dark) + .injectAppEnvironment(botRepository: .fake) + } +} diff --git a/Source/UI/Compose/ImagePickerButton.swift b/Source/UI/Compose/ImagePickerButton.swift new file mode 100644 index 0000000000..1cc8e3ed45 --- /dev/null +++ b/Source/UI/Compose/ImagePickerButton.swift @@ -0,0 +1,133 @@ +// +// ImagePickerButton.swift +// Planetary +// +// Created by Martin Dutra on 13/3/23. +// Copyright © 2023 Verse Communications Inc. All rights reserved. +// + +import Analytics +import AVKit +import SwiftUI + +struct ImagePickerButton