chore: [WIP] move core logic into package
This commit is contained in:
parent
28267fe6d8
commit
64f3d2fe3a
|
@ -0,0 +1,16 @@
|
||||||
|
import_name: 'ArkanaKeys'
|
||||||
|
namespace: 'Keys'
|
||||||
|
result_path: 'Dependencies'
|
||||||
|
flavors:
|
||||||
|
- AppStore
|
||||||
|
swift_declaration_strategy: let
|
||||||
|
should_generate_unit_tests: true
|
||||||
|
package_manager: spm
|
||||||
|
environments:
|
||||||
|
- Debug
|
||||||
|
- Release
|
||||||
|
global_secrets:
|
||||||
|
# nothing
|
||||||
|
environment_secrets:
|
||||||
|
# Will lookup for <Key>Debug and <Key>Release env vars (assuming no flavor was declared)
|
||||||
|
- NotificationEndpoint
|
|
@ -1,18 +0,0 @@
|
||||||
//
|
|
||||||
// AppShared.h
|
|
||||||
// AppShared
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-4-27.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
//! Project version number for AppShared.
|
|
||||||
FOUNDATION_EXPORT double AppSharedVersionNumber;
|
|
||||||
|
|
||||||
//! Project version string for AppShared.
|
|
||||||
FOUNDATION_EXPORT const unsigned char AppSharedVersionString[];
|
|
||||||
|
|
||||||
// In this header, you should import all the public headers of your framework using statements like #import <AppShared/PublicHeader.h>
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.4.5</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>144</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
3
Gemfile
3
Gemfile
|
@ -1,6 +1,5 @@
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem 'arkana'
|
||||||
gem "cocoapods"
|
gem "cocoapods"
|
||||||
gem "cocoapods-clean"
|
gem "cocoapods-clean"
|
||||||
gem "cocoapods-keys"
|
|
||||||
|
|
||||||
|
|
20
Gemfile.lock
20
Gemfile.lock
|
@ -3,9 +3,6 @@ GEM
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.5)
|
CFPropertyList (3.0.5)
|
||||||
rexml
|
rexml
|
||||||
RubyInline (3.12.5)
|
|
||||||
ZenTest (~> 4.3)
|
|
||||||
ZenTest (4.12.1)
|
|
||||||
activesupport (6.1.5.1)
|
activesupport (6.1.5.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
|
@ -17,6 +14,10 @@ GEM
|
||||||
algoliasearch (1.27.5)
|
algoliasearch (1.27.5)
|
||||||
httpclient (~> 2.8, >= 2.8.3)
|
httpclient (~> 2.8, >= 2.8.3)
|
||||||
json (>= 1.5.1)
|
json (>= 1.5.1)
|
||||||
|
arkana (1.2.0)
|
||||||
|
colorize (~> 0.8)
|
||||||
|
dotenv (~> 2.7)
|
||||||
|
yaml (~> 0.2)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
cocoapods (1.11.3)
|
cocoapods (1.11.3)
|
||||||
|
@ -50,9 +51,6 @@ GEM
|
||||||
typhoeus (~> 1.0)
|
typhoeus (~> 1.0)
|
||||||
cocoapods-deintegrate (1.0.5)
|
cocoapods-deintegrate (1.0.5)
|
||||||
cocoapods-downloader (1.6.3)
|
cocoapods-downloader (1.6.3)
|
||||||
cocoapods-keys (2.2.1)
|
|
||||||
dotenv
|
|
||||||
osx_keychain
|
|
||||||
cocoapods-plugins (1.0.0)
|
cocoapods-plugins (1.0.0)
|
||||||
nap
|
nap
|
||||||
cocoapods-search (1.0.1)
|
cocoapods-search (1.0.1)
|
||||||
|
@ -61,8 +59,9 @@ GEM
|
||||||
netrc (~> 0.11)
|
netrc (~> 0.11)
|
||||||
cocoapods-try (1.2.0)
|
cocoapods-try (1.2.0)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
|
colorize (0.8.1)
|
||||||
concurrent-ruby (1.1.10)
|
concurrent-ruby (1.1.10)
|
||||||
dotenv (2.7.6)
|
dotenv (2.8.1)
|
||||||
escape (0.0.4)
|
escape (0.0.4)
|
||||||
ethon (0.15.0)
|
ethon (0.15.0)
|
||||||
ffi (>= 1.15.0)
|
ffi (>= 1.15.0)
|
||||||
|
@ -79,8 +78,6 @@ GEM
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
nap (1.1.0)
|
nap (1.1.0)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
osx_keychain (1.0.2)
|
|
||||||
RubyInline (~> 3)
|
|
||||||
public_suffix (4.0.7)
|
public_suffix (4.0.7)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
ruby-macho (2.5.1)
|
ruby-macho (2.5.1)
|
||||||
|
@ -95,15 +92,16 @@ GEM
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
rexml (~> 3.2.4)
|
rexml (~> 3.2.4)
|
||||||
|
yaml (0.2.0)
|
||||||
zeitwerk (2.5.4)
|
zeitwerk (2.5.4)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
arkana
|
||||||
cocoapods
|
cocoapods
|
||||||
cocoapods-clean
|
cocoapods-clean
|
||||||
cocoapods-keys
|
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.11
|
2.3.17
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,13 +4,6 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>SchemeUserState</key>
|
<key>SchemeUserState</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>AppShared.xcscheme_^#shared#^_</key>
|
|
||||||
<dict>
|
|
||||||
<key>isShown</key>
|
|
||||||
<true/>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>6</integer>
|
|
||||||
</dict>
|
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
|
@ -19,32 +12,27 @@
|
||||||
<key>Mastodon - Profile.xcscheme_^#shared#^_</key>
|
<key>Mastodon - Profile.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>2</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>7</integer>
|
<integer>5</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
|
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>3</integer>
|
<integer>2</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - Snapshot.xcscheme_^#shared#^_</key>
|
<key>Mastodon - Snapshot.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>4</integer>
|
<integer>3</integer>
|
||||||
</dict>
|
|
||||||
<key>Mastodon - ar.xcscheme</key>
|
|
||||||
<dict>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>5</integer>
|
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - ar.xcscheme_^#shared#^_</key>
|
<key>Mastodon - ar.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>11</integer>
|
<integer>4</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - ca.xcscheme_^#shared#^_</key>
|
<key>Mastodon - ca.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -111,11 +99,6 @@
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
|
||||||
<dict>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>22</integer>
|
|
||||||
</dict>
|
|
||||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
|
@ -129,12 +112,12 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>23</integer>
|
<integer>7</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>24</integer>
|
<integer>6</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
@ -164,6 +147,11 @@
|
||||||
<key>primary</key>
|
<key>primary</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>DB8FABC526AEC7B2008E5AF4</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -19,15 +19,6 @@
|
||||||
"version": "4.2.0"
|
"version": "4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"package": "AlamofireNetworkActivityIndicator",
|
|
||||||
"repositoryURL": "https://github.com/Alamofire/AlamofireNetworkActivityIndicator",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "392bed083e8d193aca16bfa684ee24e4bcff0510",
|
|
||||||
"version": "3.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "CommonOSLog",
|
"package": "CommonOSLog",
|
||||||
"repositoryURL": "https://github.com/MainasuK/CommonOSLog",
|
"repositoryURL": "https://github.com/MainasuK/CommonOSLog",
|
||||||
|
@ -37,24 +28,6 @@
|
||||||
"version": "0.1.1"
|
"version": "0.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"package": "DiffableDataSources",
|
|
||||||
"repositoryURL": "https://github.com/MainasuK/DiffableDataSources.git",
|
|
||||||
"state": {
|
|
||||||
"branch": "feature/async-display-table",
|
|
||||||
"revision": "73393a97690959d24387c95594c045c62d9c47cf",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "DifferenceKit",
|
|
||||||
"repositoryURL": "https://github.com/ra1028/DifferenceKit.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "62745d7780deef4a023a792a1f8f763ec7bf9705",
|
|
||||||
"version": "1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "FaviconFinder",
|
"package": "FaviconFinder",
|
||||||
"repositoryURL": "https://github.com/will-lumley/FaviconFinder.git",
|
"repositoryURL": "https://github.com/will-lumley/FaviconFinder.git",
|
||||||
|
@ -159,8 +132,8 @@
|
||||||
"repositoryURL": "https://github.com/apple/swift-collections.git",
|
"repositoryURL": "https://github.com/apple/swift-collections.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "9d8719c8bebdc79740b6969c912ac706eb721d7a",
|
"revision": "f504716c27d2e5d4144fa4794b12129301d17729",
|
||||||
"version": "0.0.7"
|
"version": "1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -222,8 +195,8 @@
|
||||||
"repositoryURL": "https://github.com/uias/Tabman",
|
"repositoryURL": "https://github.com/uias/Tabman",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "a9f10cb862a32e6a22549836af013abd6b0692d3",
|
"revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465",
|
||||||
"version": "2.12.0"
|
"version": "2.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -231,8 +204,8 @@
|
||||||
"repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git",
|
"repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "779da6ce0793b461ccbbac2804755c1e29b6fa63",
|
"revision": "44c1cfaa6969963f22691aa67f88a69e3b6d651f",
|
||||||
"version": "1.8.0"
|
"version": "2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -244,6 +217,15 @@
|
||||||
"version": "2.6.1"
|
"version": "2.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"package": "UIHostingConfigurationBackport",
|
||||||
|
"repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"package": "UITextView+Placeholder",
|
"package": "UITextView+Placeholder",
|
||||||
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git",
|
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git",
|
||||||
|
|
|
@ -8,8 +8,9 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
|
||||||
import PanModal
|
import PanModal
|
||||||
|
import MastodonSDK
|
||||||
|
import MastodonCore
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import MastodonSDK
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
enum AutoCompleteSection: Equatable, Hashable {
|
enum AutoCompleteSection: Equatable, Hashable {
|
||||||
case main
|
case main
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
//
|
|
||||||
// MastodonUser.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021/2/3.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
extension MastodonUser {
|
|
||||||
|
|
||||||
public var profileURL: URL {
|
|
||||||
if let urlString = self.url,
|
|
||||||
let url = URL(string: urlString) {
|
|
||||||
return url
|
|
||||||
} else {
|
|
||||||
return URL(string: "https://\(self.domain)/@\(username)")!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var activityItems: [Any] {
|
|
||||||
var items: [Any] = []
|
|
||||||
items.append(profileURL)
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
//
|
|
||||||
// MastodonAuthenticationBox.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-7-20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
import MastodonUI
|
|
||||||
|
|
||||||
struct MastodonAuthenticationBox: UserIdentifier {
|
|
||||||
let authenticationRecord: ManagedObjectRecord<MastodonAuthentication>
|
|
||||||
let domain: String
|
|
||||||
let userID: MastodonUser.ID
|
|
||||||
let appAuthorization: Mastodon.API.OAuth.Authorization
|
|
||||||
let userAuthorization: Mastodon.API.OAuth.Authorization
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//
|
|
||||||
// MastodonEmojis.swift
|
|
||||||
// MastodonEmojis
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-9-2.
|
|
||||||
// Copyright © 2021 Twidere. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
import MastodonMeta
|
|
||||||
|
|
||||||
extension MastodonEmoji {
|
|
||||||
public convenience init(emoji: Mastodon.Entity.Emoji) {
|
|
||||||
self.init(
|
|
||||||
code: emoji.shortcode,
|
|
||||||
url: emoji.url,
|
|
||||||
staticURL: emoji.staticURL,
|
|
||||||
visibleInPicker: emoji.visibleInPicker,
|
|
||||||
category: emoji.category
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
//
|
|
||||||
// HomeTimelinePreference.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-6-21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
extension UserDefaults {
|
|
||||||
|
|
||||||
@objc dynamic var preferAsyncHomeTimeline: Bool {
|
|
||||||
get {
|
|
||||||
register(defaults: [#function: false])
|
|
||||||
return bool(forKey: #function)
|
|
||||||
}
|
|
||||||
set { self[#function] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
//
|
|
||||||
// NotificationPreference.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-4-26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import MastodonExtension
|
|
||||||
|
|
||||||
extension UserDefaults {
|
|
||||||
|
|
||||||
@objc dynamic var notificationBadgeCount: Int {
|
|
||||||
get {
|
|
||||||
register(defaults: [#function: 0])
|
|
||||||
return integer(forKey: #function)
|
|
||||||
}
|
|
||||||
set { self[#function] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
//
|
|
||||||
// ThemePreference.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-7-5.
|
|
||||||
//
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
|
import MastodonCore
|
||||||
|
import MastodonUI
|
||||||
|
|
||||||
final class AccountListViewModel {
|
final class AccountListViewModel {
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import CoreDataStack
|
||||||
import PanModal
|
import PanModal
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
final class AccountListViewController: UIViewController, NeedsDependency {
|
final class AccountListViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import Combine
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
import MastodonCore
|
||||||
|
import MastodonUI
|
||||||
|
|
||||||
final class AddAccountTableViewCell: UITableViewCell {
|
final class AddAccountTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import os.log
|
||||||
import Foundation
|
import Foundation
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension AutoCompleteViewModel {
|
extension AutoCompleteViewModel {
|
||||||
class State: GKState, NamingState {
|
class State: GKState, NamingState {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
final class AutoCompleteViewModel {
|
final class AutoCompleteViewModel {
|
||||||
|
|
||||||
|
@ -16,13 +17,13 @@ final class AutoCompleteViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let inputText = CurrentValueSubject<String, Never>("") // contains "@" or "#" prefix
|
public let inputText = CurrentValueSubject<String, Never>("") // contains "@" or "#" prefix
|
||||||
let symbolBoundingRect = CurrentValueSubject<CGRect, Never>(.zero)
|
public let symbolBoundingRect = CurrentValueSubject<CGRect, Never>(.zero)
|
||||||
let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
public let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
||||||
|
|
||||||
// output
|
// output
|
||||||
var autoCompleteItems = CurrentValueSubject<[AutoCompleteItem], Never>([])
|
public var autoCompleteItems = CurrentValueSubject<[AutoCompleteItem], Never>([])
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<AutoCompleteSection, AutoCompleteItem>!
|
public var diffableDataSource: UITableViewDiffableDataSource<AutoCompleteSection, AutoCompleteItem>!
|
||||||
private(set) lazy var stateMachine: GKStateMachine = {
|
private(set) lazy var stateMachine: GKStateMachine = {
|
||||||
// exclude timeline middle fetcher state
|
// exclude timeline middle fetcher state
|
||||||
let stateMachine = GKStateMachine(states: [
|
let stateMachine = GKStateMachine(states: [
|
||||||
|
|
|
@ -27,7 +27,7 @@ final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
weak var delegate: ComposeStatusAttachmentCollectionViewCellDelegate?
|
weak var delegate: ComposeStatusAttachmentCollectionViewCellDelegate?
|
||||||
|
|
||||||
let attachmentContainerView = AttachmentContainerView()
|
// let attachmentContainerView = AttachmentContainerView()
|
||||||
let removeButton: UIButton = {
|
let removeButton: UIButton = {
|
||||||
let button = HighlightDimmableButton()
|
let button = HighlightDimmableButton()
|
||||||
button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
|
button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
|
||||||
|
@ -45,11 +45,11 @@ final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell {
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
|
||||||
attachmentContainerView.activityIndicatorView.startAnimating()
|
// attachmentContainerView.activityIndicatorView.startAnimating()
|
||||||
attachmentContainerView.previewImageView.af.cancelImageRequest()
|
// attachmentContainerView.previewImageView.af.cancelImageRequest()
|
||||||
attachmentContainerView.previewImageView.image = .placeholder(color: .systemFill)
|
// attachmentContainerView.previewImageView.image = .placeholder(color: .systemFill)
|
||||||
delegate = nil
|
// delegate = nil
|
||||||
disposeBag.removeAll()
|
// disposeBag.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
|
@ -73,31 +73,30 @@ extension ComposeStatusAttachmentCollectionViewCell {
|
||||||
private func _init() {
|
private func _init() {
|
||||||
// selectionStyle = .none
|
// selectionStyle = .none
|
||||||
|
|
||||||
attachmentContainerView.translatesAutoresizingMaskIntoConstraints = false
|
// attachmentContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
contentView.addSubview(attachmentContainerView)
|
// contentView.addSubview(attachmentContainerView)
|
||||||
NSLayoutConstraint.activate([
|
// NSLayoutConstraint.activate([
|
||||||
attachmentContainerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: ComposeStatusAttachmentCollectionViewCell.verticalMarginHeight),
|
// attachmentContainerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: ComposeStatusAttachmentCollectionViewCell.verticalMarginHeight),
|
||||||
attachmentContainerView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
// attachmentContainerView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
attachmentContainerView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
// attachmentContainerView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
||||||
contentView.bottomAnchor.constraint(equalTo: attachmentContainerView.bottomAnchor, constant: ComposeStatusAttachmentCollectionViewCell.verticalMarginHeight),
|
// contentView.bottomAnchor.constraint(equalTo: attachmentContainerView.bottomAnchor, constant: ComposeStatusAttachmentCollectionViewCell.verticalMarginHeight),
|
||||||
attachmentContainerView.heightAnchor.constraint(equalToConstant: 205).priority(.defaultHigh),
|
// attachmentContainerView.heightAnchor.constraint(equalToConstant: 205).priority(.defaultHigh),
|
||||||
])
|
// ])
|
||||||
|
//
|
||||||
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
// removeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
contentView.addSubview(removeButton)
|
// contentView.addSubview(removeButton)
|
||||||
NSLayoutConstraint.activate([
|
// NSLayoutConstraint.activate([
|
||||||
removeButton.centerXAnchor.constraint(equalTo: attachmentContainerView.trailingAnchor),
|
// removeButton.centerXAnchor.constraint(equalTo: attachmentContainerView.trailingAnchor),
|
||||||
removeButton.centerYAnchor.constraint(equalTo: attachmentContainerView.topAnchor),
|
// removeButton.centerYAnchor.constraint(equalTo: attachmentContainerView.topAnchor),
|
||||||
removeButton.widthAnchor.constraint(equalToConstant: ComposeStatusAttachmentCollectionViewCell.removeButtonSize.width).priority(.defaultHigh),
|
// removeButton.widthAnchor.constraint(equalToConstant: ComposeStatusAttachmentCollectionViewCell.removeButtonSize.width).priority(.defaultHigh),
|
||||||
removeButton.heightAnchor.constraint(equalToConstant: ComposeStatusAttachmentCollectionViewCell.removeButtonSize.height).priority(.defaultHigh),
|
// removeButton.heightAnchor.constraint(equalToConstant: ComposeStatusAttachmentCollectionViewCell.removeButtonSize.height).priority(.defaultHigh),
|
||||||
])
|
// ])
|
||||||
|
//
|
||||||
removeButton.addTarget(self, action: #selector(ComposeStatusAttachmentCollectionViewCell.removeButtonDidPressed(_:)), for: .touchUpInside)
|
// removeButton.addTarget(self, action: #selector(ComposeStatusAttachmentCollectionViewCell.removeButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extension ComposeStatusAttachmentCollectionViewCell {
|
extension ComposeStatusAttachmentCollectionViewCell {
|
||||||
|
|
||||||
@objc private func removeButtonDidPressed(_ sender: UIButton) {
|
@objc private func removeButtonDidPressed(_ sender: UIButton) {
|
||||||
|
|
|
@ -74,17 +74,23 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
publishButton.setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
|
publishButton.setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
let tableView: ComposeTableView = {
|
let scrollView: UIScrollView = {
|
||||||
let tableView = ComposeTableView()
|
let scrollView = UIScrollView()
|
||||||
tableView.register(ComposeRepliedToStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeRepliedToStatusContentTableViewCell.self))
|
scrollView.alwaysBounceVertical = true
|
||||||
tableView.register(ComposeStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusContentTableViewCell.self))
|
return scrollView
|
||||||
tableView.register(ComposeStatusAttachmentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self))
|
|
||||||
tableView.alwaysBounceVertical = true
|
|
||||||
tableView.separatorStyle = .none
|
|
||||||
tableView.tableFooterView = UIView()
|
|
||||||
return tableView
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// let tableView: ComposeTableView = {
|
||||||
|
// let tableView = ComposeTableView()
|
||||||
|
// tableView.register(ComposeRepliedToStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeRepliedToStatusContentTableViewCell.self))
|
||||||
|
// tableView.register(ComposeStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusContentTableViewCell.self))
|
||||||
|
// tableView.register(ComposeStatusAttachmentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self))
|
||||||
|
// tableView.alwaysBounceVertical = true
|
||||||
|
// tableView.separatorStyle = .none
|
||||||
|
// tableView.tableFooterView = UIView()
|
||||||
|
// return tableView
|
||||||
|
// }()
|
||||||
|
|
||||||
var systemKeyboardHeight: CGFloat = .zero {
|
var systemKeyboardHeight: CGFloat = .zero {
|
||||||
didSet {
|
didSet {
|
||||||
// note: some system AutoLayout warning here
|
// note: some system AutoLayout warning here
|
||||||
|
@ -202,13 +208,13 @@ extension ComposeViewController {
|
||||||
publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
|
|
||||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(tableView)
|
view.addSubview(scrollView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
composeToolbarView.translatesAutoresizingMaskIntoConstraints = false
|
composeToolbarView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -232,318 +238,320 @@ extension ComposeViewController {
|
||||||
view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor),
|
view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
tableView.delegate = self
|
// tableView.delegate = self
|
||||||
viewModel.setupDataSource(
|
// viewModel.setupDataSource(
|
||||||
tableView: tableView,
|
// tableView: tableView,
|
||||||
metaTextDelegate: self,
|
// metaTextDelegate: self,
|
||||||
metaTextViewDelegate: self,
|
// metaTextViewDelegate: self,
|
||||||
customEmojiPickerInputViewModel: viewModel.customEmojiPickerInputViewModel,
|
// customEmojiPickerInputViewModel: viewModel.customEmojiPickerInputViewModel,
|
||||||
composeStatusAttachmentCollectionViewCellDelegate: self,
|
// composeStatusAttachmentCollectionViewCellDelegate: self,
|
||||||
composeStatusPollOptionCollectionViewCellDelegate: self,
|
// composeStatusPollOptionCollectionViewCellDelegate: self,
|
||||||
composeStatusPollOptionAppendEntryCollectionViewCellDelegate: self,
|
// composeStatusPollOptionAppendEntryCollectionViewCellDelegate: self,
|
||||||
composeStatusPollExpiresOptionCollectionViewCellDelegate: self
|
// composeStatusPollExpiresOptionCollectionViewCellDelegate: self
|
||||||
)
|
// )
|
||||||
|
|
||||||
viewModel.composeStatusAttribute.$composeContent
|
// viewModel.composeStatusAttribute.$composeContent
|
||||||
.removeDuplicates()
|
// .removeDuplicates()
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
// .sink { [weak self] _ in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
guard self.view.window != nil else { return }
|
// guard self.view.window != nil else { return }
|
||||||
UIView.performWithoutAnimation {
|
// UIView.performWithoutAnimation {
|
||||||
self.tableView.beginUpdates()
|
// self.tableView.beginUpdates()
|
||||||
self.tableView.endUpdates()
|
// self.tableView.setNeedsLayout()
|
||||||
}
|
// self.tableView.layoutIfNeeded()
|
||||||
}
|
// self.tableView.endUpdates()
|
||||||
.store(in: &disposeBag)
|
// }
|
||||||
|
// }
|
||||||
|
// .store(in: &disposeBag)
|
||||||
|
|
||||||
customEmojiPickerInputView.collectionView.delegate = self
|
// customEmojiPickerInputView.collectionView.delegate = self
|
||||||
viewModel.customEmojiPickerInputViewModel.customEmojiPickerInputView = customEmojiPickerInputView
|
// viewModel.customEmojiPickerInputViewModel.customEmojiPickerInputView = customEmojiPickerInputView
|
||||||
viewModel.setupCustomEmojiPickerDiffableDataSource(
|
// viewModel.setupCustomEmojiPickerDiffableDataSource(
|
||||||
for: customEmojiPickerInputView.collectionView,
|
// for: customEmojiPickerInputView.collectionView,
|
||||||
dependency: self
|
// dependency: self
|
||||||
)
|
// )
|
||||||
|
|
||||||
viewModel.composeStatusContentTableViewCell.delegate = self
|
// viewModel.composeStatusContentTableViewCell.delegate = self
|
||||||
|
//
|
||||||
// update layout when keyboard show/dismiss
|
// // update layout when keyboard show/dismiss
|
||||||
view.layoutIfNeeded()
|
// view.layoutIfNeeded()
|
||||||
|
//
|
||||||
let keyboardHasShortcutBar = CurrentValueSubject<Bool, Never>(traitCollection.userInterfaceIdiom == .pad) // update default value later
|
// let keyboardHasShortcutBar = CurrentValueSubject<Bool, Never>(traitCollection.userInterfaceIdiom == .pad) // update default value later
|
||||||
let keyboardEventPublishers = Publishers.CombineLatest3(
|
// let keyboardEventPublishers = Publishers.CombineLatest3(
|
||||||
KeyboardResponderService.shared.isShow,
|
// KeyboardResponderService.shared.isShow,
|
||||||
KeyboardResponderService.shared.state,
|
// KeyboardResponderService.shared.state,
|
||||||
KeyboardResponderService.shared.endFrame
|
// KeyboardResponderService.shared.endFrame
|
||||||
)
|
// )
|
||||||
Publishers.CombineLatest3(
|
// Publishers.CombineLatest3(
|
||||||
keyboardEventPublishers,
|
// keyboardEventPublishers,
|
||||||
viewModel.$isCustomEmojiComposing,
|
// viewModel.$isCustomEmojiComposing,
|
||||||
viewModel.$autoCompleteInfo
|
// viewModel.$autoCompleteInfo
|
||||||
)
|
// )
|
||||||
.sink(receiveValue: { [weak self] keyboardEvents, isCustomEmojiComposing, autoCompleteInfo in
|
// .sink(receiveValue: { [weak self] keyboardEvents, isCustomEmojiComposing, autoCompleteInfo in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
|
//
|
||||||
let (isShow, state, endFrame) = keyboardEvents
|
// let (isShow, state, endFrame) = keyboardEvents
|
||||||
|
//
|
||||||
switch self.traitCollection.userInterfaceIdiom {
|
// switch self.traitCollection.userInterfaceIdiom {
|
||||||
case .pad:
|
// case .pad:
|
||||||
keyboardHasShortcutBar.value = state != .floating
|
// keyboardHasShortcutBar.value = state != .floating
|
||||||
default:
|
// default:
|
||||||
keyboardHasShortcutBar.value = false
|
// keyboardHasShortcutBar.value = false
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
let extraMargin: CGFloat = {
|
// let extraMargin: CGFloat = {
|
||||||
var margin = self.composeToolbarView.frame.height
|
// var margin = self.composeToolbarView.frame.height
|
||||||
if autoCompleteInfo != nil {
|
// if autoCompleteInfo != nil {
|
||||||
margin += ComposeViewController.minAutoCompleteVisibleHeight
|
// margin += ComposeViewController.minAutoCompleteVisibleHeight
|
||||||
}
|
// }
|
||||||
return margin
|
// return margin
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
guard isShow, state == .dock else {
|
// guard isShow, state == .dock else {
|
||||||
self.tableView.contentInset.bottom = extraMargin
|
// self.tableView.contentInset.bottom = extraMargin
|
||||||
self.tableView.verticalScrollIndicatorInsets.bottom = extraMargin
|
// self.tableView.verticalScrollIndicatorInsets.bottom = extraMargin
|
||||||
|
//
|
||||||
if let superView = self.autoCompleteViewController.tableView.superview {
|
// if let superView = self.autoCompleteViewController.tableView.superview {
|
||||||
let autoCompleteTableViewBottomInset: CGFloat = {
|
// let autoCompleteTableViewBottomInset: CGFloat = {
|
||||||
let tableViewFrameInWindow = superView.convert(self.autoCompleteViewController.tableView.frame, to: nil)
|
// let tableViewFrameInWindow = superView.convert(self.autoCompleteViewController.tableView.frame, to: nil)
|
||||||
let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - self.view.frame.maxY
|
// let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - self.view.frame.maxY
|
||||||
return max(0, padding)
|
// return max(0, padding)
|
||||||
}()
|
// }()
|
||||||
self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset
|
// self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset
|
||||||
self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset
|
// self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
UIView.animate(withDuration: 0.3) {
|
// UIView.animate(withDuration: 0.3) {
|
||||||
self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom
|
// self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom
|
||||||
if self.view.window != nil {
|
// if self.view.window != nil {
|
||||||
self.view.layoutIfNeeded()
|
// self.view.layoutIfNeeded()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
// isShow AND dock state
|
// // isShow AND dock state
|
||||||
self.systemKeyboardHeight = endFrame.height
|
// self.systemKeyboardHeight = endFrame.height
|
||||||
|
//
|
||||||
// adjust inset for auto-complete
|
// // adjust inset for auto-complete
|
||||||
let autoCompleteTableViewBottomInset: CGFloat = {
|
// let autoCompleteTableViewBottomInset: CGFloat = {
|
||||||
guard let superview = self.autoCompleteViewController.tableView.superview else { return .zero }
|
// guard let superview = self.autoCompleteViewController.tableView.superview else { return .zero }
|
||||||
let tableViewFrameInWindow = superview.convert(self.autoCompleteViewController.tableView.frame, to: nil)
|
// let tableViewFrameInWindow = superview.convert(self.autoCompleteViewController.tableView.frame, to: nil)
|
||||||
let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - endFrame.minY
|
// let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - endFrame.minY
|
||||||
return max(0, padding)
|
// return max(0, padding)
|
||||||
}()
|
// }()
|
||||||
self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset
|
// self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset
|
||||||
self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset
|
// self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset
|
||||||
|
//
|
||||||
// adjust inset for tableView
|
// // adjust inset for tableView
|
||||||
let contentFrame = self.view.convert(self.tableView.frame, to: nil)
|
// let contentFrame = self.view.convert(self.tableView.frame, to: nil)
|
||||||
let padding = contentFrame.maxY + extraMargin - endFrame.minY
|
// let padding = contentFrame.maxY + extraMargin - endFrame.minY
|
||||||
guard padding > 0 else {
|
// guard padding > 0 else {
|
||||||
self.tableView.contentInset.bottom = self.view.safeAreaInsets.bottom + extraMargin
|
// self.tableView.contentInset.bottom = self.view.safeAreaInsets.bottom + extraMargin
|
||||||
self.tableView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom + extraMargin
|
// self.tableView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom + extraMargin
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
self.tableView.contentInset.bottom = padding - self.view.safeAreaInsets.bottom
|
// self.tableView.contentInset.bottom = padding - self.view.safeAreaInsets.bottom
|
||||||
self.tableView.verticalScrollIndicatorInsets.bottom = padding - self.view.safeAreaInsets.bottom
|
// self.tableView.verticalScrollIndicatorInsets.bottom = padding - self.view.safeAreaInsets.bottom
|
||||||
UIView.animate(withDuration: 0.3) {
|
// UIView.animate(withDuration: 0.3) {
|
||||||
self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height
|
// self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height
|
||||||
self.view.layoutIfNeeded()
|
// self.view.layoutIfNeeded()
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind auto-complete
|
// // bind auto-complete
|
||||||
viewModel.$autoCompleteInfo
|
// viewModel.$autoCompleteInfo
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] info in
|
// .sink { [weak self] info in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
let textEditorView = self.textEditorView
|
// let textEditorView = self.textEditorView
|
||||||
if self.autoCompleteViewController.view.superview == nil {
|
// if self.autoCompleteViewController.view.superview == nil {
|
||||||
self.autoCompleteViewController.view.frame = self.view.bounds
|
// self.autoCompleteViewController.view.frame = self.view.bounds
|
||||||
// add to container view. seealso: `viewDidLayoutSubviews()`
|
// // add to container view. seealso: `viewDidLayoutSubviews()`
|
||||||
self.viewModel.composeStatusContentTableViewCell.textEditorViewContainerView.addSubview(self.autoCompleteViewController.view)
|
// self.viewModel.composeStatusContentTableViewCell.textEditorViewContainerView.addSubview(self.autoCompleteViewController.view)
|
||||||
self.addChild(self.autoCompleteViewController)
|
// self.addChild(self.autoCompleteViewController)
|
||||||
self.autoCompleteViewController.didMove(toParent: self)
|
// self.autoCompleteViewController.didMove(toParent: self)
|
||||||
self.autoCompleteViewController.view.isHidden = true
|
// self.autoCompleteViewController.view.isHidden = true
|
||||||
self.tableView.autoCompleteViewController = self.autoCompleteViewController
|
// self.tableView.autoCompleteViewController = self.autoCompleteViewController
|
||||||
}
|
// }
|
||||||
self.updateAutoCompleteViewControllerLayout()
|
// self.updateAutoCompleteViewControllerLayout()
|
||||||
self.autoCompleteViewController.view.isHidden = info == nil
|
// self.autoCompleteViewController.view.isHidden = info == nil
|
||||||
guard let info = info else { return }
|
// guard let info = info else { return }
|
||||||
let symbolBoundingRectInContainer = textEditorView.textView.convert(info.symbolBoundingRect, to: self.autoCompleteViewController.chevronView)
|
// let symbolBoundingRectInContainer = textEditorView.textView.convert(info.symbolBoundingRect, to: self.autoCompleteViewController.chevronView)
|
||||||
self.autoCompleteViewController.view.frame.origin.y = info.textBoundingRect.maxY
|
// self.autoCompleteViewController.view.frame.origin.y = info.textBoundingRect.maxY
|
||||||
self.autoCompleteViewController.viewModel.symbolBoundingRect.value = symbolBoundingRectInContainer
|
// self.autoCompleteViewController.viewModel.symbolBoundingRect.value = symbolBoundingRectInContainer
|
||||||
self.autoCompleteViewController.viewModel.inputText.value = String(info.inputText)
|
// self.autoCompleteViewController.viewModel.inputText.value = String(info.inputText)
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind publish bar button state
|
// // bind publish bar button state
|
||||||
viewModel.$isPublishBarButtonItemEnabled
|
// viewModel.$isPublishBarButtonItemEnabled
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.assign(to: \.isEnabled, on: publishButton)
|
// .assign(to: \.isEnabled, on: publishButton)
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind media button toolbar state
|
// // bind media button toolbar state
|
||||||
viewModel.$isMediaToolbarButtonEnabled
|
// viewModel.$isMediaToolbarButtonEnabled
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isMediaToolbarButtonEnabled in
|
// .sink { [weak self] isMediaToolbarButtonEnabled in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.composeToolbarView.mediaBarButtonItem.isEnabled = isMediaToolbarButtonEnabled
|
// self.composeToolbarView.mediaBarButtonItem.isEnabled = isMediaToolbarButtonEnabled
|
||||||
self.composeToolbarView.mediaButton.isEnabled = isMediaToolbarButtonEnabled
|
// self.composeToolbarView.mediaButton.isEnabled = isMediaToolbarButtonEnabled
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind poll button toolbar state
|
// // bind poll button toolbar state
|
||||||
viewModel.$isPollToolbarButtonEnabled
|
// viewModel.$isPollToolbarButtonEnabled
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isPollToolbarButtonEnabled in
|
// .sink { [weak self] isPollToolbarButtonEnabled in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.composeToolbarView.pollBarButtonItem.isEnabled = isPollToolbarButtonEnabled
|
// self.composeToolbarView.pollBarButtonItem.isEnabled = isPollToolbarButtonEnabled
|
||||||
self.composeToolbarView.pollButton.isEnabled = isPollToolbarButtonEnabled
|
// self.composeToolbarView.pollButton.isEnabled = isPollToolbarButtonEnabled
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
Publishers.CombineLatest(
|
// Publishers.CombineLatest(
|
||||||
viewModel.$isPollComposing,
|
// viewModel.$isPollComposing,
|
||||||
viewModel.$isPollToolbarButtonEnabled
|
// viewModel.$isPollToolbarButtonEnabled
|
||||||
)
|
// )
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isPollComposing, isPollToolbarButtonEnabled in
|
// .sink { [weak self] isPollComposing, isPollToolbarButtonEnabled in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
guard isPollToolbarButtonEnabled else {
|
// guard isPollToolbarButtonEnabled else {
|
||||||
let accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
|
// let accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
|
||||||
self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
|
||||||
self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
let accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll
|
// let accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll
|
||||||
self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
|
||||||
self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind image picker toolbar state
|
// // bind image picker toolbar state
|
||||||
viewModel.$attachmentServices
|
// viewModel.$attachmentServices
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] attachmentServices in
|
// .sink { [weak self] attachmentServices in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
let isEnabled = attachmentServices.count < self.viewModel.maxMediaAttachments
|
// let isEnabled = attachmentServices.count < self.viewModel.maxMediaAttachments
|
||||||
self.composeToolbarView.mediaBarButtonItem.isEnabled = isEnabled
|
// self.composeToolbarView.mediaBarButtonItem.isEnabled = isEnabled
|
||||||
self.composeToolbarView.mediaButton.isEnabled = isEnabled
|
// self.composeToolbarView.mediaButton.isEnabled = isEnabled
|
||||||
self.resetImagePicker()
|
// self.resetImagePicker()
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind content warning button state
|
// // bind content warning button state
|
||||||
viewModel.$isContentWarningComposing
|
// viewModel.$isContentWarningComposing
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isContentWarningComposing in
|
// .sink { [weak self] isContentWarningComposing in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
let accessibilityLabel = isContentWarningComposing ? L10n.Scene.Compose.Accessibility.disableContentWarning : L10n.Scene.Compose.Accessibility.enableContentWarning
|
// let accessibilityLabel = isContentWarningComposing ? L10n.Scene.Compose.Accessibility.disableContentWarning : L10n.Scene.Compose.Accessibility.enableContentWarning
|
||||||
self.composeToolbarView.contentWarningBarButtonItem.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.contentWarningBarButtonItem.accessibilityLabel = accessibilityLabel
|
||||||
self.composeToolbarView.contentWarningButton.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.contentWarningButton.accessibilityLabel = accessibilityLabel
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind visibility toolbar UI
|
// // bind visibility toolbar UI
|
||||||
Publishers.CombineLatest(
|
// Publishers.CombineLatest(
|
||||||
viewModel.$selectedStatusVisibility,
|
// viewModel.$selectedStatusVisibility,
|
||||||
viewModel.traitCollectionDidChangePublisher
|
// viewModel.traitCollectionDidChangePublisher
|
||||||
)
|
// )
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] type, _ in
|
// .sink { [weak self] type, _ in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
// let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
||||||
self.composeToolbarView.visibilityBarButtonItem.image = image
|
// self.composeToolbarView.visibilityBarButtonItem.image = image
|
||||||
self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
|
// self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
|
||||||
self.composeToolbarView.activeVisibilityType.value = type
|
// self.composeToolbarView.activeVisibilityType.value = type
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
viewModel.$characterCount
|
// viewModel.$characterCount
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] characterCount in
|
// .sink { [weak self] characterCount in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
let count = self.viewModel.composeContentLimit - characterCount
|
// let count = self.viewModel.composeContentLimit - characterCount
|
||||||
self.composeToolbarView.characterCountLabel.text = "\(count)"
|
// self.composeToolbarView.characterCountLabel.text = "\(count)"
|
||||||
self.characterCountLabel.text = "\(count)"
|
// self.characterCountLabel.text = "\(count)"
|
||||||
let font: UIFont
|
// let font: UIFont
|
||||||
let textColor: UIColor
|
// let textColor: UIColor
|
||||||
let accessibilityLabel: String
|
// let accessibilityLabel: String
|
||||||
switch count {
|
// switch count {
|
||||||
case _ where count < 0:
|
// case _ where count < 0:
|
||||||
font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold)
|
// font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold)
|
||||||
textColor = Asset.Colors.danger.color
|
// textColor = Asset.Colors.danger.color
|
||||||
accessibilityLabel = L10n.A11y.Plural.Count.inputLimitExceeds(abs(count))
|
// accessibilityLabel = L10n.A11y.Plural.Count.inputLimitExceeds(abs(count))
|
||||||
default:
|
// default:
|
||||||
font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular)
|
// font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular)
|
||||||
textColor = Asset.Colors.Label.secondary.color
|
// textColor = Asset.Colors.Label.secondary.color
|
||||||
accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(count)
|
// accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(count)
|
||||||
}
|
// }
|
||||||
self.composeToolbarView.characterCountLabel.font = font
|
// self.composeToolbarView.characterCountLabel.font = font
|
||||||
self.composeToolbarView.characterCountLabel.textColor = textColor
|
// self.composeToolbarView.characterCountLabel.textColor = textColor
|
||||||
self.composeToolbarView.characterCountLabel.accessibilityLabel = accessibilityLabel
|
// self.composeToolbarView.characterCountLabel.accessibilityLabel = accessibilityLabel
|
||||||
self.characterCountLabel.font = font
|
// self.characterCountLabel.font = font
|
||||||
self.characterCountLabel.textColor = textColor
|
// self.characterCountLabel.textColor = textColor
|
||||||
self.characterCountLabel.accessibilityLabel = accessibilityLabel
|
// self.characterCountLabel.accessibilityLabel = accessibilityLabel
|
||||||
self.characterCountLabel.sizeToFit()
|
// self.characterCountLabel.sizeToFit()
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// bind custom emoji picker UI
|
// // bind custom emoji picker UI
|
||||||
viewModel.customEmojiViewModel?.emojis
|
// viewModel.customEmojiViewModel?.emojis
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink(receiveValue: { [weak self] emojis in
|
// .sink(receiveValue: { [weak self] emojis in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
if emojis.isEmpty {
|
// if emojis.isEmpty {
|
||||||
self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
|
// self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
|
||||||
} else {
|
// } else {
|
||||||
self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
|
// self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
// setup snap behavior
|
// // setup snap behavior
|
||||||
Publishers.CombineLatest(
|
// Publishers.CombineLatest(
|
||||||
viewModel.$repliedToCellFrame,
|
// viewModel.$repliedToCellFrame,
|
||||||
viewModel.$collectionViewState
|
// viewModel.$collectionViewState
|
||||||
)
|
// )
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] repliedToCellFrame, collectionViewState in
|
// .sink { [weak self] repliedToCellFrame, collectionViewState in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
guard repliedToCellFrame != .zero else { return }
|
// guard repliedToCellFrame != .zero else { return }
|
||||||
switch collectionViewState {
|
// switch collectionViewState {
|
||||||
case .fold:
|
// case .fold:
|
||||||
self.tableView.contentInset.top = -repliedToCellFrame.height
|
// self.tableView.contentInset.top = -repliedToCellFrame.height
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: set contentInset.top: -%s", ((#file as NSString).lastPathComponent), #line, #function, repliedToCellFrame.height.description)
|
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: set contentInset.top: -%s", ((#file as NSString).lastPathComponent), #line, #function, repliedToCellFrame.height.description)
|
||||||
|
//
|
||||||
case .expand:
|
// case .expand:
|
||||||
self.tableView.contentInset.top = 0
|
// self.tableView.contentInset.top = 0
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
|
//
|
||||||
configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar.value)
|
// configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar.value)
|
||||||
Publishers.CombineLatest(
|
// Publishers.CombineLatest(
|
||||||
keyboardHasShortcutBar,
|
// keyboardHasShortcutBar,
|
||||||
viewModel.traitCollectionDidChangePublisher
|
// viewModel.traitCollectionDidChangePublisher
|
||||||
)
|
// )
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] keyboardHasShortcutBar, _ in
|
// .sink { [weak self] keyboardHasShortcutBar, _ in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar)
|
// self.configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar)
|
||||||
}
|
// }
|
||||||
.store(in: &disposeBag)
|
// .store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
// update MetaText without trigger call underlaying `UITextStorage.processEditing`
|
// // update MetaText without trigger call underlaying `UITextStorage.processEditing`
|
||||||
_ = textEditorView.processEditing(textEditorView.textStorage)
|
// _ = textEditorView.processEditing(textEditorView.textStorage)
|
||||||
|
|
||||||
markTextEditorViewBecomeFirstResponser()
|
// markTextEditorViewBecomeFirstResponser()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
@ -678,8 +686,8 @@ extension ComposeViewController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
view.backgroundColor = backgroundColor
|
view.backgroundColor = backgroundColor
|
||||||
tableView.backgroundColor = backgroundColor
|
// tableView.backgroundColor = backgroundColor
|
||||||
composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor
|
// composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyboard shortcutBar
|
// keyboard shortcutBar
|
||||||
|
@ -991,53 +999,53 @@ extension ComposeViewController: ComposeToolbarViewDelegate {
|
||||||
|
|
||||||
// MARK: - UIScrollViewDelegate
|
// MARK: - UIScrollViewDelegate
|
||||||
extension ComposeViewController {
|
extension ComposeViewController {
|
||||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
// func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||||
guard scrollView === tableView else { return }
|
// guard scrollView === tableView else { return }
|
||||||
|
//
|
||||||
let repliedToCellFrame = viewModel.repliedToCellFrame
|
// let repliedToCellFrame = viewModel.repliedToCellFrame
|
||||||
guard repliedToCellFrame != .zero else { return }
|
// guard repliedToCellFrame != .zero else { return }
|
||||||
|
//
|
||||||
// try to find some patterns:
|
// // try to find some patterns:
|
||||||
// print("""
|
// // print("""
|
||||||
// repliedToCellFrame: \(viewModel.repliedToCellFrame.value.height)
|
// // repliedToCellFrame: \(viewModel.repliedToCellFrame.value.height)
|
||||||
// scrollView.contentOffset.y: \(scrollView.contentOffset.y)
|
// // scrollView.contentOffset.y: \(scrollView.contentOffset.y)
|
||||||
// scrollView.contentSize.height: \(scrollView.contentSize.height)
|
// // scrollView.contentSize.height: \(scrollView.contentSize.height)
|
||||||
// scrollView.frame: \(scrollView.frame)
|
// // scrollView.frame: \(scrollView.frame)
|
||||||
// scrollView.adjustedContentInset.top: \(scrollView.adjustedContentInset.top)
|
// // scrollView.adjustedContentInset.top: \(scrollView.adjustedContentInset.top)
|
||||||
// scrollView.adjustedContentInset.bottom: \(scrollView.adjustedContentInset.bottom)
|
// // scrollView.adjustedContentInset.bottom: \(scrollView.adjustedContentInset.bottom)
|
||||||
// """)
|
// // """)
|
||||||
|
//
|
||||||
switch viewModel.collectionViewState {
|
// switch viewModel.collectionViewState {
|
||||||
case .fold:
|
// case .fold:
|
||||||
os_log("%{public}s[%{public}ld], %{public}s: fold", ((#file as NSString).lastPathComponent), #line, #function)
|
// os_log("%{public}s[%{public}ld], %{public}s: fold", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard velocity.y < 0 else { return }
|
// guard velocity.y < 0 else { return }
|
||||||
let offsetY = scrollView.contentOffset.y + scrollView.adjustedContentInset.top
|
// let offsetY = scrollView.contentOffset.y + scrollView.adjustedContentInset.top
|
||||||
if offsetY < -44 {
|
// if offsetY < -44 {
|
||||||
tableView.contentInset.top = 0
|
// tableView.contentInset.top = 0
|
||||||
targetContentOffset.pointee = CGPoint(x: 0, y: -scrollView.adjustedContentInset.top)
|
// targetContentOffset.pointee = CGPoint(x: 0, y: -scrollView.adjustedContentInset.top)
|
||||||
viewModel.collectionViewState = .expand
|
// viewModel.collectionViewState = .expand
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
case .expand:
|
// case .expand:
|
||||||
os_log("%{public}s[%{public}ld], %{public}s: expand", ((#file as NSString).lastPathComponent), #line, #function)
|
// os_log("%{public}s[%{public}ld], %{public}s: expand", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard velocity.y > 0 else { return }
|
// guard velocity.y > 0 else { return }
|
||||||
// check if top across
|
// // check if top across
|
||||||
let topOffset = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) - repliedToCellFrame.height
|
// let topOffset = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) - repliedToCellFrame.height
|
||||||
|
//
|
||||||
// check if bottom bounce
|
// // check if bottom bounce
|
||||||
let bottomOffsetY = scrollView.contentOffset.y + (scrollView.frame.height - scrollView.adjustedContentInset.bottom)
|
// let bottomOffsetY = scrollView.contentOffset.y + (scrollView.frame.height - scrollView.adjustedContentInset.bottom)
|
||||||
let bottomOffset = bottomOffsetY - scrollView.contentSize.height
|
// let bottomOffset = bottomOffsetY - scrollView.contentSize.height
|
||||||
|
//
|
||||||
if topOffset > 44 {
|
// if topOffset > 44 {
|
||||||
// do not interrupt user scrolling
|
// // do not interrupt user scrolling
|
||||||
viewModel.collectionViewState = .fold
|
// viewModel.collectionViewState = .fold
|
||||||
} else if bottomOffset > 44 {
|
// } else if bottomOffset > 44 {
|
||||||
tableView.contentInset.top = -repliedToCellFrame.height
|
// tableView.contentInset.top = -repliedToCellFrame.height
|
||||||
targetContentOffset.pointee = CGPoint(x: 0, y: -repliedToCellFrame.height)
|
// targetContentOffset.pointee = CGPoint(x: 0, y: -repliedToCellFrame.height)
|
||||||
viewModel.collectionViewState = .fold
|
// viewModel.collectionViewState = .fold
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
|
|
@ -6,10 +6,12 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
import UIHostingConfigurationBackport
|
||||||
|
|
||||||
final class ComposeStatusAttachmentTableViewCell: UITableViewCell {
|
final class ComposeStatusAttachmentTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
@ -75,85 +77,91 @@ extension ComposeStatusAttachmentTableViewCell {
|
||||||
}
|
}
|
||||||
.store(in: &observations)
|
.store(in: &observations)
|
||||||
|
|
||||||
self.dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [
|
self.dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
|
||||||
weak self
|
[weak self] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||||
] collectionView, indexPath, item -> UICollectionViewCell? in
|
|
||||||
guard let self = self else { return UICollectionViewCell() }
|
guard let self = self else { return UICollectionViewCell() }
|
||||||
switch item {
|
switch item {
|
||||||
case .attachment(let attachmentService):
|
case .attachment(let attachmentService):
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell
|
||||||
cell.attachmentContainerView.descriptionTextView.text = attachmentService.description.value
|
cell.contentConfiguration = UIHostingConfigurationBackport {
|
||||||
cell.delegate = self.composeStatusAttachmentCollectionViewCellDelegate
|
HStack {
|
||||||
attachmentService.thumbnailImage
|
Image(systemName: "star")
|
||||||
.receive(on: DispatchQueue.main)
|
Text("Favorites")
|
||||||
.sink { [weak cell] thumbnailImage in
|
Spacer()
|
||||||
guard let cell = cell else { return }
|
|
||||||
let size = cell.attachmentContainerView.previewImageView.frame.size != .zero ? cell.attachmentContainerView.previewImageView.frame.size : CGSize(width: 1, height: 1)
|
|
||||||
guard let image = thumbnailImage else {
|
|
||||||
let placeholder = UIImage.placeholder(
|
|
||||||
size: size,
|
|
||||||
color: ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor
|
|
||||||
)
|
|
||||||
.af.imageRounded(
|
|
||||||
withCornerRadius: AttachmentContainerView.containerViewCornerRadius
|
|
||||||
)
|
|
||||||
cell.attachmentContainerView.previewImageView.image = placeholder
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// cannot get correct size. set corner radius on layer
|
|
||||||
cell.attachmentContainerView.previewImageView.image = image
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
Publishers.CombineLatest(
|
|
||||||
attachmentService.uploadStateMachineSubject.eraseToAnyPublisher(),
|
|
||||||
attachmentService.error.eraseToAnyPublisher()
|
|
||||||
)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak cell, weak attachmentService] uploadState, error in
|
|
||||||
guard let cell = cell else { return }
|
|
||||||
guard let attachmentService = attachmentService else { return }
|
|
||||||
cell.attachmentContainerView.emptyStateView.isHidden = error == nil
|
|
||||||
cell.attachmentContainerView.descriptionBackgroundView.isHidden = error != nil
|
|
||||||
if let error = error {
|
|
||||||
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
|
||||||
cell.attachmentContainerView.emptyStateView.label.text = error.localizedDescription
|
|
||||||
} else {
|
|
||||||
guard let uploadState = uploadState else { return }
|
|
||||||
switch uploadState {
|
|
||||||
case is MastodonAttachmentService.UploadState.Finish:
|
|
||||||
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
|
||||||
case is MastodonAttachmentService.UploadState.Fail:
|
|
||||||
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
|
||||||
// FIXME: not display
|
|
||||||
cell.attachmentContainerView.emptyStateView.label.text = {
|
|
||||||
if let file = attachmentService.file.value {
|
|
||||||
switch file {
|
|
||||||
case .jpeg, .png, .gif:
|
|
||||||
return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
|
||||||
case .other:
|
|
||||||
return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.video)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
// cell.attachmentContainerView.descriptionTextView.text = attachmentService.description.value
|
||||||
NotificationCenter.default.publisher(
|
// cell.delegate = self.composeStatusAttachmentCollectionViewCellDelegate
|
||||||
for: UITextView.textDidChangeNotification,
|
// attachmentService.thumbnailImage
|
||||||
object: cell.attachmentContainerView.descriptionTextView
|
// .receive(on: DispatchQueue.main)
|
||||||
)
|
// .sink { [weak cell] thumbnailImage in
|
||||||
.receive(on: DispatchQueue.main)
|
// guard let cell = cell else { return }
|
||||||
.sink { notification in
|
// let size = cell.attachmentContainerView.previewImageView.frame.size != .zero ? cell.attachmentContainerView.previewImageView.frame.size : CGSize(width: 1, height: 1)
|
||||||
guard let textField = notification.object as? UITextView else { return }
|
// guard let image = thumbnailImage else {
|
||||||
let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
// let placeholder = UIImage.placeholder(
|
||||||
attachmentService.description.value = text
|
// size: size,
|
||||||
}
|
// color: ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor
|
||||||
.store(in: &cell.disposeBag)
|
// )
|
||||||
|
// .af.imageRounded(
|
||||||
|
// withCornerRadius: AttachmentContainerView.containerViewCornerRadius
|
||||||
|
// )
|
||||||
|
// cell.attachmentContainerView.previewImageView.image = placeholder
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// // cannot get correct size. set corner radius on layer
|
||||||
|
// cell.attachmentContainerView.previewImageView.image = image
|
||||||
|
// }
|
||||||
|
// .store(in: &cell.disposeBag)
|
||||||
|
// Publishers.CombineLatest(
|
||||||
|
// attachmentService.uploadStateMachineSubject.eraseToAnyPublisher(),
|
||||||
|
// attachmentService.error.eraseToAnyPublisher()
|
||||||
|
// )
|
||||||
|
// .receive(on: DispatchQueue.main)
|
||||||
|
// .sink { [weak cell, weak attachmentService] uploadState, error in
|
||||||
|
// guard let cell = cell else { return }
|
||||||
|
// guard let attachmentService = attachmentService else { return }
|
||||||
|
// cell.attachmentContainerView.emptyStateView.isHidden = error == nil
|
||||||
|
// cell.attachmentContainerView.descriptionBackgroundView.isHidden = error != nil
|
||||||
|
// if let error = error {
|
||||||
|
// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||||
|
// cell.attachmentContainerView.emptyStateView.label.text = error.localizedDescription
|
||||||
|
// } else {
|
||||||
|
// guard let uploadState = uploadState else { return }
|
||||||
|
// switch uploadState {
|
||||||
|
// case is MastodonAttachmentService.UploadState.Finish:
|
||||||
|
// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||||
|
// case is MastodonAttachmentService.UploadState.Fail:
|
||||||
|
// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||||
|
// // FIXME: not display
|
||||||
|
// cell.attachmentContainerView.emptyStateView.label.text = {
|
||||||
|
// if let file = attachmentService.file.value {
|
||||||
|
// switch file {
|
||||||
|
// case .jpeg, .png, .gif:
|
||||||
|
// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||||
|
// case .other:
|
||||||
|
// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.video)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
// default:
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .store(in: &cell.disposeBag)
|
||||||
|
// NotificationCenter.default.publisher(
|
||||||
|
// for: UITextView.textDidChangeNotification,
|
||||||
|
// object: cell.attachmentContainerView.descriptionTextView
|
||||||
|
// )
|
||||||
|
// .receive(on: DispatchQueue.main)
|
||||||
|
// .sink { notification in
|
||||||
|
// guard let textField = notification.object as? UITextView else { return }
|
||||||
|
// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
// attachmentService.description.value = text
|
||||||
|
// }
|
||||||
|
// .store(in: &cell.disposeBag)
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,61 +6,63 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import UITextView_Placeholder
|
import SwiftUI
|
||||||
import MastodonAsset
|
import MastodonUI
|
||||||
import MastodonLocalization
|
|
||||||
|
|
||||||
final class AttachmentContainerView: UIView {
|
final class AttachmentContainerView: UIView {
|
||||||
|
|
||||||
static let containerViewCornerRadius: CGFloat = 4
|
static let containerViewCornerRadius: CGFloat = 4
|
||||||
|
|
||||||
var descriptionBackgroundViewFrameObservation: NSKeyValueObservation?
|
// var descriptionBackgroundViewFrameObservation: NSKeyValueObservation?
|
||||||
|
//
|
||||||
|
// let activityIndicatorView: UIActivityIndicatorView = {
|
||||||
|
// let activityIndicatorView = UIActivityIndicatorView(style: .large)
|
||||||
|
// activityIndicatorView.color = UIColor.white.withAlphaComponent(0.8)
|
||||||
|
// return activityIndicatorView
|
||||||
|
// }()
|
||||||
|
//
|
||||||
|
// let previewImageView: UIImageView = {
|
||||||
|
// let imageView = UIImageView()
|
||||||
|
// imageView.contentMode = .scaleAspectFill
|
||||||
|
// imageView.layer.cornerRadius = AttachmentContainerView.containerViewCornerRadius
|
||||||
|
// imageView.layer.cornerCurve = .continuous
|
||||||
|
// imageView.layer.masksToBounds = true
|
||||||
|
// return imageView
|
||||||
|
// }()
|
||||||
|
//
|
||||||
|
// let emptyStateView = AttachmentContainerView.EmptyStateView()
|
||||||
|
// let descriptionBackgroundView: UIView = {
|
||||||
|
// let view = UIView()
|
||||||
|
// view.layer.masksToBounds = true
|
||||||
|
// view.layer.cornerRadius = AttachmentContainerView.containerViewCornerRadius
|
||||||
|
// view.layer.cornerCurve = .continuous
|
||||||
|
// view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||||
|
// view.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 5, right: 8)
|
||||||
|
// return view
|
||||||
|
// }()
|
||||||
|
// let descriptionBackgroundGradientLayer: CAGradientLayer = {
|
||||||
|
// let gradientLayer = CAGradientLayer()
|
||||||
|
// gradientLayer.colors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.69).cgColor]
|
||||||
|
// gradientLayer.locations = [0.0, 1.0]
|
||||||
|
// gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
|
||||||
|
// gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
|
||||||
|
// gradientLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
|
||||||
|
// return gradientLayer
|
||||||
|
// }()
|
||||||
|
// let descriptionTextView: UITextView = {
|
||||||
|
// let textView = UITextView()
|
||||||
|
// textView.showsVerticalScrollIndicator = false
|
||||||
|
// textView.backgroundColor = .clear
|
||||||
|
// textView.textColor = .white
|
||||||
|
// textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15), maximumPointSize: 20)
|
||||||
|
// textView.placeholder = L10n.Scene.Compose.Attachment.descriptionPhoto
|
||||||
|
// textView.placeholderColor = UIColor.white.withAlphaComponent(0.6) // force white with alpha for Light/Dark mode
|
||||||
|
// textView.returnKeyType = .done
|
||||||
|
// return textView
|
||||||
|
// }()
|
||||||
|
|
||||||
let activityIndicatorView: UIActivityIndicatorView = {
|
private(set) lazy var contentView = AttachmentView(viewModel: viewModel)
|
||||||
let activityIndicatorView = UIActivityIndicatorView(style: .large)
|
public var viewModel: AttachmentView.ViewModel!
|
||||||
activityIndicatorView.color = UIColor.white.withAlphaComponent(0.8)
|
|
||||||
return activityIndicatorView
|
|
||||||
}()
|
|
||||||
|
|
||||||
let previewImageView: UIImageView = {
|
|
||||||
let imageView = UIImageView()
|
|
||||||
imageView.contentMode = .scaleAspectFill
|
|
||||||
imageView.layer.cornerRadius = AttachmentContainerView.containerViewCornerRadius
|
|
||||||
imageView.layer.cornerCurve = .continuous
|
|
||||||
imageView.layer.masksToBounds = true
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
|
|
||||||
let emptyStateView = AttachmentContainerView.EmptyStateView()
|
|
||||||
let descriptionBackgroundView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.layer.masksToBounds = true
|
|
||||||
view.layer.cornerRadius = AttachmentContainerView.containerViewCornerRadius
|
|
||||||
view.layer.cornerCurve = .continuous
|
|
||||||
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
|
||||||
view.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 5, right: 8)
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
let descriptionBackgroundGradientLayer: CAGradientLayer = {
|
|
||||||
let gradientLayer = CAGradientLayer()
|
|
||||||
gradientLayer.colors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.69).cgColor]
|
|
||||||
gradientLayer.locations = [0.0, 1.0]
|
|
||||||
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
|
|
||||||
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
|
|
||||||
gradientLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
|
|
||||||
return gradientLayer
|
|
||||||
}()
|
|
||||||
let descriptionTextView: UITextView = {
|
|
||||||
let textView = UITextView()
|
|
||||||
textView.showsVerticalScrollIndicator = false
|
|
||||||
textView.backgroundColor = .clear
|
|
||||||
textView.textColor = .white
|
|
||||||
textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15), maximumPointSize: 20)
|
|
||||||
textView.placeholder = L10n.Scene.Compose.Attachment.descriptionPhoto
|
|
||||||
textView.placeholderColor = UIColor.white.withAlphaComponent(0.6) // force white with alpha for Light/Dark mode
|
|
||||||
textView.returnKeyType = .done
|
|
||||||
return textView
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
@ -77,89 +79,99 @@ final class AttachmentContainerView: UIView {
|
||||||
extension AttachmentContainerView {
|
extension AttachmentContainerView {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
previewImageView.translatesAutoresizingMaskIntoConstraints = false
|
let hostingViewController = UIHostingController(rootView: contentView)
|
||||||
addSubview(previewImageView)
|
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(hostingViewController.view)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
previewImageView.topAnchor.constraint(equalTo: topAnchor),
|
hostingViewController.view.topAnchor.constraint(equalTo: topAnchor),
|
||||||
previewImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
hostingViewController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
previewImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
hostingViewController.view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
previewImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
hostingViewController.view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
descriptionBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
// previewImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addSubview(descriptionBackgroundView)
|
// addSubview(previewImageView)
|
||||||
NSLayoutConstraint.activate([
|
// NSLayoutConstraint.activate([
|
||||||
descriptionBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
// previewImageView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
descriptionBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
// previewImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
descriptionBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
// previewImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
descriptionBackgroundView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.3),
|
// previewImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
// ])
|
||||||
descriptionBackgroundView.layer.addSublayer(descriptionBackgroundGradientLayer)
|
//
|
||||||
descriptionBackgroundViewFrameObservation = descriptionBackgroundView.observe(\.bounds, options: [.initial, .new]) { [weak self] _, _ in
|
// descriptionBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
guard let self = self else { return }
|
// addSubview(descriptionBackgroundView)
|
||||||
self.descriptionBackgroundGradientLayer.frame = self.descriptionBackgroundView.bounds
|
// NSLayoutConstraint.activate([
|
||||||
}
|
// descriptionBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
// descriptionBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
descriptionTextView.translatesAutoresizingMaskIntoConstraints = false
|
// descriptionBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
descriptionBackgroundView.addSubview(descriptionTextView)
|
// descriptionBackgroundView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.3),
|
||||||
NSLayoutConstraint.activate([
|
// ])
|
||||||
descriptionTextView.leadingAnchor.constraint(equalTo: descriptionBackgroundView.layoutMarginsGuide.leadingAnchor),
|
// descriptionBackgroundView.layer.addSublayer(descriptionBackgroundGradientLayer)
|
||||||
descriptionTextView.trailingAnchor.constraint(equalTo: descriptionBackgroundView.layoutMarginsGuide.trailingAnchor),
|
// descriptionBackgroundViewFrameObservation = descriptionBackgroundView.observe(\.bounds, options: [.initial, .new]) { [weak self] _, _ in
|
||||||
descriptionBackgroundView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: descriptionTextView.bottomAnchor),
|
// guard let self = self else { return }
|
||||||
descriptionTextView.heightAnchor.constraint(lessThanOrEqualToConstant: 36),
|
// self.descriptionBackgroundGradientLayer.frame = self.descriptionBackgroundView.bounds
|
||||||
])
|
// }
|
||||||
|
//
|
||||||
emptyStateView.translatesAutoresizingMaskIntoConstraints = false
|
// descriptionTextView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addSubview(emptyStateView)
|
// descriptionBackgroundView.addSubview(descriptionTextView)
|
||||||
NSLayoutConstraint.activate([
|
// NSLayoutConstraint.activate([
|
||||||
emptyStateView.topAnchor.constraint(equalTo: topAnchor),
|
// descriptionTextView.leadingAnchor.constraint(equalTo: descriptionBackgroundView.layoutMarginsGuide.leadingAnchor),
|
||||||
emptyStateView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
// descriptionTextView.trailingAnchor.constraint(equalTo: descriptionBackgroundView.layoutMarginsGuide.trailingAnchor),
|
||||||
emptyStateView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
// descriptionBackgroundView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: descriptionTextView.bottomAnchor),
|
||||||
emptyStateView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
// descriptionTextView.heightAnchor.constraint(lessThanOrEqualToConstant: 36),
|
||||||
])
|
// ])
|
||||||
|
//
|
||||||
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
// emptyStateView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addSubview(activityIndicatorView)
|
// addSubview(emptyStateView)
|
||||||
NSLayoutConstraint.activate([
|
// NSLayoutConstraint.activate([
|
||||||
activityIndicatorView.centerXAnchor.constraint(equalTo: previewImageView.centerXAnchor),
|
// emptyStateView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
activityIndicatorView.centerYAnchor.constraint(equalTo: previewImageView.centerYAnchor),
|
// emptyStateView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
])
|
// emptyStateView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
// emptyStateView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
setupBroader()
|
// ])
|
||||||
|
//
|
||||||
emptyStateView.isHidden = true
|
// activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
activityIndicatorView.hidesWhenStopped = true
|
// addSubview(activityIndicatorView)
|
||||||
activityIndicatorView.startAnimating()
|
// NSLayoutConstraint.activate([
|
||||||
|
// activityIndicatorView.centerXAnchor.constraint(equalTo: previewImageView.centerXAnchor),
|
||||||
descriptionTextView.delegate = self
|
// activityIndicatorView.centerYAnchor.constraint(equalTo: previewImageView.centerYAnchor),
|
||||||
|
// ])
|
||||||
|
//
|
||||||
|
// setupBroader()
|
||||||
|
//
|
||||||
|
// emptyStateView.isHidden = true
|
||||||
|
// activityIndicatorView.hidesWhenStopped = true
|
||||||
|
// activityIndicatorView.startAnimating()
|
||||||
|
//
|
||||||
|
// descriptionTextView.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
// super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
//
|
||||||
setupBroader()
|
// setupBroader()
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AttachmentContainerView {
|
extension AttachmentContainerView {
|
||||||
|
|
||||||
private func setupBroader() {
|
// private func setupBroader() {
|
||||||
emptyStateView.layer.borderWidth = 1
|
// emptyStateView.layer.borderWidth = 1
|
||||||
emptyStateView.layer.borderColor = traitCollection.userInterfaceStyle == .dark ? ThemeService.shared.currentTheme.value.tableViewCellSelectionBackgroundColor.cgColor : nil
|
// emptyStateView.layer.borderColor = traitCollection.userInterfaceStyle == .dark ? ThemeService.shared.currentTheme.value.tableViewCellSelectionBackgroundColor.cgColor : nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITextViewDelegate
|
//// MARK: - UITextViewDelegate
|
||||||
extension AttachmentContainerView: UITextViewDelegate {
|
//extension AttachmentContainerView: UITextViewDelegate {
|
||||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
// func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
// let keyboard dismiss when input description with "done" type return key
|
// // let keyboard dismiss when input description with "done" type return key
|
||||||
if textView === descriptionTextView, text == "\n" {
|
// if textView === descriptionTextView, text == "\n" {
|
||||||
textView.resignFirstResponder()
|
// textView.resignFirstResponder()
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import GameplayKit
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
final class DiscoveryCommunityViewModel {
|
final class DiscoveryCommunityViewModel {
|
||||||
|
|
||||||
|
|
|
@ -77,31 +77,8 @@ final class NotificationTimelineViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationTimelineViewModel {
|
extension NotificationTimelineViewModel {
|
||||||
enum Scope: Hashable, CaseIterable {
|
|
||||||
case everything
|
|
||||||
case mentions
|
|
||||||
|
|
||||||
var includeTypes: [MastodonNotificationType]? {
|
typealias Scope = APIService.NotificationScope
|
||||||
switch self {
|
|
||||||
case .everything: return nil
|
|
||||||
case .mentions: return [.mention, .status]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var excludeTypes: [MastodonNotificationType]? {
|
|
||||||
switch self {
|
|
||||||
case .everything: return nil
|
|
||||||
case .mentions: return [.follow, .followRequest, .reblog, .favourite, .poll]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _excludeTypes: [Mastodon.Entity.Notification.NotificationType]? {
|
|
||||||
switch self {
|
|
||||||
case .everything: return nil
|
|
||||||
case .mentions: return [.follow, .followRequest, .reblog, .favourite, .poll]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func feedPredicate(
|
static func feedPredicate(
|
||||||
authenticationBox: MastodonAuthenticationBox,
|
authenticationBox: MastodonAuthenticationBox,
|
||||||
|
|
|
@ -12,9 +12,6 @@ import AuthenticationServices
|
||||||
|
|
||||||
final class MastodonAuthenticationController {
|
final class MastodonAuthenticationController {
|
||||||
|
|
||||||
static let callbackURLScheme = "mastodon"
|
|
||||||
static let callbackURL = "mastodon://joinmastodon.org/oauth"
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
// input
|
// input
|
||||||
|
@ -43,7 +40,7 @@ extension MastodonAuthenticationController {
|
||||||
private func authentication() {
|
private func authentication() {
|
||||||
authenticationSession = ASWebAuthenticationSession(
|
authenticationSession = ASWebAuthenticationSession(
|
||||||
url: authenticateURL,
|
url: authenticateURL,
|
||||||
callbackURLScheme: MastodonAuthenticationController.callbackURLScheme
|
callbackURLScheme: APIService.callbackURLScheme
|
||||||
) { [weak self] callback, error in
|
) { [weak self] callback, error in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
os_log("%{public}s[%{public}ld], %{public}s: callback: %s, error: %s", ((#file as NSString).lastPathComponent), #line, #function, callback?.debugDescription ?? "<nil>", error.debugDescription)
|
os_log("%{public}s[%{public}ld], %{public}s: callback: %s, error: %s", ((#file as NSString).lastPathComponent), #line, #function, callback?.debugDescription ?? "<nil>", error.debugDescription)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
final class RootSplitViewController: UISplitViewController, NeedsDependency {
|
final class RootSplitViewController: UISplitViewController, NeedsDependency {
|
||||||
|
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
//
|
|
||||||
// StatusPublishService.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-3-26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import Foundation
|
|
||||||
import Intents
|
|
||||||
import Combine
|
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
final class StatusPublishService {
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.StatusPublishService.working-queue")
|
|
||||||
|
|
||||||
// input
|
|
||||||
var viewModels = CurrentValueSubject<[ComposeViewModel], Never>([]) // use strong reference to retain the view models
|
|
||||||
|
|
||||||
// output
|
|
||||||
let composeViewModelDidUpdatePublisher = PassthroughSubject<Void, Never>()
|
|
||||||
let latestPublishingComposeViewModel = CurrentValueSubject<ComposeViewModel?, Never>(nil)
|
|
||||||
|
|
||||||
init() {
|
|
||||||
Publishers.CombineLatest(
|
|
||||||
viewModels.eraseToAnyPublisher(),
|
|
||||||
composeViewModelDidUpdatePublisher.eraseToAnyPublisher()
|
|
||||||
)
|
|
||||||
.map { viewModels, _ in viewModels.last }
|
|
||||||
.assign(to: \.value, on: latestPublishingComposeViewModel)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusPublishService {
|
|
||||||
|
|
||||||
func publish(composeViewModel: ComposeViewModel) {
|
|
||||||
workingQueue.sync {
|
|
||||||
guard !self.viewModels.value.contains(where: { $0 === composeViewModel }) else { return }
|
|
||||||
self.viewModels.value = self.viewModels.value + [composeViewModel]
|
|
||||||
|
|
||||||
composeViewModel.publishStateMachinePublisher
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self, weak composeViewModel] state in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard let composeViewModel = composeViewModel else { return }
|
|
||||||
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: composeViewModelDidUpdate", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
self.composeViewModelDidUpdatePublisher.send()
|
|
||||||
|
|
||||||
switch state {
|
|
||||||
case is ComposeViewModel.PublishState.Finish:
|
|
||||||
self.remove(composeViewModel: composeViewModel)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &composeViewModel.disposeBag) // cancel subscription when viewModel dealloc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove(composeViewModel: ComposeViewModel) {
|
|
||||||
workingQueue.async {
|
|
||||||
var viewModels = self.viewModels.value
|
|
||||||
viewModels.removeAll(where: { $0 === composeViewModel })
|
|
||||||
self.viewModels.value = viewModels
|
|
||||||
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: composeViewModel removed", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// DocumentStore.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-1-27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
class DocumentStore: ObservableObject {
|
|
||||||
let appStartUpTimestamp = Date()
|
|
||||||
var defaultRevealStatusDict: [Mastodon.Entity.Status.ID: Bool] = [:]
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
//
|
|
||||||
// ViewStateStore.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-1-27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
struct ViewStateStore {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ViewState { }
|
|
|
@ -8,9 +8,9 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import AppShared
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
@_exported import MastodonUI
|
import MastodonCore
|
||||||
|
import MastodonUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
final class SendPostIntentHandler: NSObject {
|
final class SendPostIntentHandler: NSObject {
|
||||||
|
|
||||||
|
@ -18,8 +19,12 @@ final class SendPostIntentHandler: NSObject {
|
||||||
|
|
||||||
let coreDataStack = CoreDataStack()
|
let coreDataStack = CoreDataStack()
|
||||||
lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext
|
lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext
|
||||||
lazy var api = APIService.shared
|
lazy var api: APIService = {
|
||||||
|
let backgroundManagedObjectContext = coreDataStack.newTaskContext()
|
||||||
|
return APIService(
|
||||||
|
backgroundManagedObjectContext: backgroundManagedObjectContext
|
||||||
|
)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SendPostIntentHandling
|
// MARK: - SendPostIntentHandling
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import Intents
|
import Intents
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension Account {
|
extension Account {
|
||||||
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// APIService.swift
|
|
||||||
// MastodonIntent
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-7-26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
// Replica APIService for share extension
|
|
||||||
final class APIService {
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
static let shared = APIService()
|
|
||||||
|
|
||||||
// internal
|
|
||||||
let session: URLSession
|
|
||||||
|
|
||||||
// output
|
|
||||||
let error = PassthroughSubject<APIError, Never>()
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
self.session = URLSession(configuration: .default)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "Alamofire",
|
||||||
|
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b",
|
||||||
|
"version": "5.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "AlamofireImage",
|
||||||
|
"repositoryURL": "https://github.com/Alamofire/AlamofireImage.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10",
|
||||||
|
"version": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "CommonOSLog",
|
||||||
|
"repositoryURL": "https://github.com/MainasuK/CommonOSLog",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "c121624a30698e9886efe38aebb36ff51c01b6c2",
|
||||||
|
"version": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "FaviconFinder",
|
||||||
|
"repositoryURL": "https://github.com/will-lumley/FaviconFinder.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a",
|
||||||
|
"version": "3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "FLAnimatedImage",
|
||||||
|
"repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "d4f07b6f164d53c1212c3e54d6460738b1981e9f",
|
||||||
|
"version": "1.0.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "FPSIndicator",
|
||||||
|
"repositoryURL": "https://github.com/MainasuK/FPSIndicator.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "e4a5067ccd5293b024c767f09e51056afd4a4796",
|
||||||
|
"version": "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Fuzi",
|
||||||
|
"repositoryURL": "https://github.com/cezheng/Fuzi.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f08c8323da21e985f3772610753bcfc652c2103f",
|
||||||
|
"version": "3.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "KeychainAccess",
|
||||||
|
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||||
|
"version": "4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "MetaTextKit",
|
||||||
|
"repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "dcd5255d6930c2fab408dc8562c577547e477624",
|
||||||
|
"version": "2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Nuke",
|
||||||
|
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97",
|
||||||
|
"version": "10.11.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "NukeFLAnimatedImagePlugin",
|
||||||
|
"repositoryURL": "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b59c346a7d536336db3b0f12c72c6e53ee709e16",
|
||||||
|
"version": "8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Pageboy",
|
||||||
|
"repositoryURL": "https://github.com/uias/Pageboy",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "af8fa81788b893205e1ff42ddd88c5b0b315d7c5",
|
||||||
|
"version": "3.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "PanModal",
|
||||||
|
"repositoryURL": "https://github.com/slackhq/PanModal.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b012aecb6b67a8e46369227f893c12544846613f",
|
||||||
|
"version": "1.2.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SDWebImage",
|
||||||
|
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "9248fe561a2a153916fb9597e3af4434784c6d32",
|
||||||
|
"version": "5.13.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-collections",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-collections.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f504716c27d2e5d4144fa4794b12129301d17729",
|
||||||
|
"version": "1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "546610d52b19be3e19935e0880bb06b9c03f5cef",
|
||||||
|
"version": "1.14.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-zlib-support",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftSoup",
|
||||||
|
"repositoryURL": "https://github.com/scinfu/SwiftSoup.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "6778575285177365cbad3e5b8a72f2a20583cfec",
|
||||||
|
"version": "2.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Introspect",
|
||||||
|
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f2616860a41f9d9932da412a8978fec79c06fe24",
|
||||||
|
"version": "0.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftyJSON",
|
||||||
|
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
|
||||||
|
"version": "5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "TabBarPager",
|
||||||
|
"repositoryURL": "https://github.com/TwidereProject/TabBarPager.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "488aa66d157a648901b61721212c0dec23d27ee5",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Tabman",
|
||||||
|
"repositoryURL": "https://github.com/uias/Tabman",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465",
|
||||||
|
"version": "2.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "ThirdPartyMailer",
|
||||||
|
"repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "44c1cfaa6969963f22691aa67f88a69e3b6d651f",
|
||||||
|
"version": "2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "TOCropViewController",
|
||||||
|
"repositoryURL": "https://github.com/TimOliver/TOCropViewController.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "d0470491f56e734731bbf77991944c0dfdee3e0e",
|
||||||
|
"version": "2.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "UIHostingConfigurationBackport",
|
||||||
|
"repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "UITextView+Placeholder",
|
||||||
|
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "20f513ded04a040cdf5467f0891849b1763ede3b",
|
||||||
|
"version": "1.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ let package = Package(
|
||||||
"CoreDataStack",
|
"CoreDataStack",
|
||||||
"MastodonAsset",
|
"MastodonAsset",
|
||||||
"MastodonCommon",
|
"MastodonCommon",
|
||||||
|
"MastodonCore",
|
||||||
"MastodonExtension",
|
"MastodonExtension",
|
||||||
"MastodonLocalization",
|
"MastodonLocalization",
|
||||||
"MastodonSDK",
|
"MastodonSDK",
|
||||||
|
@ -23,17 +24,30 @@ let package = Package(
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"),
|
.package(name: "ArkanaKeys", path: "../dependencies/ArkanaKeys"),
|
||||||
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
|
.package(name: "FaviconFinder", url: "https://github.com/will-lumley/FaviconFinder.git", from: "3.2.2"),
|
||||||
.package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"),
|
.package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3"),
|
||||||
.package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"),
|
.package(name: "UITextView+Placeholder", url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"),
|
||||||
.package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("2.2.5")),
|
|
||||||
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.0"),
|
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.0"),
|
||||||
.package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"),
|
.package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"),
|
||||||
.package(name: "NukeFLAnimatedImagePlugin", url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"),
|
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.3"),
|
||||||
.package(name: "UITextView+Placeholder", url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"),
|
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
|
||||||
.package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3"),
|
.package(url: "https://github.com/cezheng/Fuzi.git", from: "3.1.3"),
|
||||||
.package(name: "FaviconFinder", url: "https://github.com/will-lumley/FaviconFinder.git", from: "3.2.2"),
|
.package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"),
|
||||||
|
.package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"),
|
||||||
|
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", from: "4.2.2"),
|
||||||
|
.package(url: "https://github.com/MainasuK/CommonOSLog", from: "0.1.1"),
|
||||||
|
.package(url: "https://github.com/MainasuK/FPSIndicator.git", from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/slackhq/PanModal.git", from: "1.2.7"),
|
||||||
|
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"),
|
||||||
|
.package(url: "https://github.com/TimOliver/TOCropViewController.git", from: "2.6.1"),
|
||||||
|
.package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("2.2.5")),
|
||||||
|
.package(url: "https://github.com/TwidereProject/TabBarPager.git", from: "0.1.0"),
|
||||||
|
.package(url: "https://github.com/uias/Tabman", from: "2.13.0"),
|
||||||
|
.package(url: "https://github.com/vtourraine/ThirdPartyMailer.git", from: "2.1.0"),
|
||||||
|
.package(url: "https://github.com/woxtu/UIHostingConfigurationBackport.git", from: "0.1.0"),
|
||||||
|
.package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.12.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
@ -60,6 +74,22 @@ let package = Package(
|
||||||
"MastodonExtension"
|
"MastodonExtension"
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "MastodonCore",
|
||||||
|
dependencies: [
|
||||||
|
"CoreDataStack",
|
||||||
|
"MastodonAsset",
|
||||||
|
"MastodonCommon",
|
||||||
|
"MastodonLocalization",
|
||||||
|
"MastodonSDK",
|
||||||
|
.product(name: "Alamofire", package: "Alamofire"),
|
||||||
|
.product(name: "AlamofireImage", package: "AlamofireImage"),
|
||||||
|
.product(name: "CommonOSLog", package: "CommonOSLog"),
|
||||||
|
.product(name: "ArkanaKeys", package: "ArkanaKeys"),
|
||||||
|
.product(name: "KeychainAccess", package: "KeychainAccess"),
|
||||||
|
.product(name: "MetaTextKit", package: "MetaTextKit")
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "MastodonExtension",
|
name: "MastodonExtension",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
|
@ -78,20 +108,20 @@ let package = Package(
|
||||||
.target(
|
.target(
|
||||||
name: "MastodonUI",
|
name: "MastodonUI",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"CoreDataStack",
|
"MastodonCore",
|
||||||
"MastodonSDK",
|
|
||||||
"MastodonExtension",
|
|
||||||
"MastodonAsset",
|
|
||||||
"MastodonLocalization",
|
|
||||||
.product(name: "Alamofire", package: "Alamofire"),
|
|
||||||
.product(name: "AlamofireImage", package: "AlamofireImage"),
|
|
||||||
.product(name: "FLAnimatedImage", package: "FLAnimatedImage"),
|
.product(name: "FLAnimatedImage", package: "FLAnimatedImage"),
|
||||||
.product(name: "FaviconFinder", package: "FaviconFinder"),
|
.product(name: "FaviconFinder", package: "FaviconFinder"),
|
||||||
.product(name: "MetaTextKit", package: "MetaTextKit"),
|
|
||||||
.product(name: "Nuke", package: "Nuke"),
|
.product(name: "Nuke", package: "Nuke"),
|
||||||
.product(name: "NukeFLAnimatedImagePlugin", package: "NukeFLAnimatedImagePlugin"),
|
|
||||||
.product(name: "Introspect", package: "Introspect"),
|
.product(name: "Introspect", package: "Introspect"),
|
||||||
.product(name: "UITextView+Placeholder", package: "UITextView+Placeholder"),
|
.product(name: "UITextView+Placeholder", package: "UITextView+Placeholder"),
|
||||||
|
.product(name: "UIHostingConfigurationBackport", package: "UIHostingConfigurationBackport"),
|
||||||
|
.product(name: "TabBarPager", package: "TabBarPager"),
|
||||||
|
.product(name: "ThirdPartyMailer", package: "ThirdPartyMailer"),
|
||||||
|
.product(name: "OrderedCollections", package: "swift-collections"),
|
||||||
|
.product(name: "Tabman", package: "Tabman"),
|
||||||
|
.product(name: "MetaTextKit", package: "MetaTextKit"),
|
||||||
|
.product(name: "CropViewController", package: "TOCropViewController"),
|
||||||
|
.product(name: "PanModal", package: "PanModal"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
|
|
|
@ -113,6 +113,15 @@ public final class CoreDataStack {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CoreDataStack {
|
||||||
|
public func newTaskContext() -> NSManagedObjectContext {
|
||||||
|
let taskContext = persistentContainer.newBackgroundContext()
|
||||||
|
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||||
|
taskContext.undoManager = nil
|
||||||
|
return taskContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension CoreDataStack {
|
extension CoreDataStack {
|
||||||
|
|
||||||
public func rebuild() {
|
public func rebuild() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import UIKit
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
|
|
||||||
@objc dynamic var preferredUsingDefaultBrowser: Bool {
|
@objc public dynamic var preferredUsingDefaultBrowser: Bool {
|
||||||
get {
|
get {
|
||||||
register(defaults: [#function: false])
|
register(defaults: [#function: false])
|
||||||
return bool(forKey: #function)
|
return bool(forKey: #function)
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
import MastodonExtension
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
// always use hash value (SHA256) from accessToken as key
|
// always use hash value (SHA256) from accessToken as key
|
||||||
|
@ -38,3 +39,15 @@ extension UserDefaults {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension UserDefaults {
|
||||||
|
|
||||||
|
@objc public dynamic var notificationBadgeCount: Int {
|
||||||
|
get {
|
||||||
|
register(defaults: [#function: 0])
|
||||||
|
return integer(forKey: #function)
|
||||||
|
}
|
||||||
|
set { self[#function] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,14 +9,14 @@ import Foundation
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
|
|
||||||
@objc dynamic var processCompletedCount: Int {
|
@objc public dynamic var processCompletedCount: Int {
|
||||||
get {
|
get {
|
||||||
return integer(forKey: #function)
|
return integer(forKey: #function)
|
||||||
}
|
}
|
||||||
set { self[#function] = newValue }
|
set { self[#function] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc dynamic var lastVersionPromptedForReview: String? {
|
@objc public dynamic var lastVersionPromptedForReview: String? {
|
||||||
get {
|
get {
|
||||||
return string(forKey: #function)
|
return string(forKey: #function)
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
@objc dynamic var didShowMultipleAccountSwitchWizard: Bool {
|
@objc public dynamic var didShowMultipleAccountSwitchWizard: Bool {
|
||||||
get { return bool(forKey: #function) }
|
get { return bool(forKey: #function) }
|
||||||
set { self[#function] = newValue }
|
set { self[#function] = newValue }
|
||||||
}
|
}
|
|
@ -1,44 +1,42 @@
|
||||||
//
|
//
|
||||||
// AppContext.swift
|
// AppContext.swift
|
||||||
// Mastodon
|
|
||||||
//
|
//
|
||||||
// Created by Cirno MainasuK on 2021-1-27.
|
//
|
||||||
|
// Created by MainasuK on 22/9/30.
|
||||||
//
|
//
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import MastodonUI
|
|
||||||
|
|
||||||
class AppContext: ObservableObject {
|
public class AppContext: ObservableObject {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
@Published var viewStateStore = ViewStateStore()
|
public let coreDataStack: CoreDataStack
|
||||||
|
public let managedObjectContext: NSManagedObjectContext
|
||||||
|
public let backgroundManagedObjectContext: NSManagedObjectContext
|
||||||
|
|
||||||
let coreDataStack: CoreDataStack
|
public let apiService: APIService
|
||||||
let managedObjectContext: NSManagedObjectContext
|
public let authenticationService: AuthenticationService
|
||||||
let backgroundManagedObjectContext: NSManagedObjectContext
|
public let emojiService: EmojiService
|
||||||
|
public let statusPublishService = StatusPublishService()
|
||||||
|
public let notificationService: NotificationService
|
||||||
|
public let settingService: SettingService
|
||||||
|
public let instanceService: InstanceService
|
||||||
|
|
||||||
let apiService: APIService
|
public let blockDomainService: BlockDomainService
|
||||||
let authenticationService: AuthenticationService
|
public let statusFilterService: StatusFilterService
|
||||||
let emojiService: EmojiService
|
public let photoLibraryService = PhotoLibraryService()
|
||||||
let statusPublishService = StatusPublishService()
|
|
||||||
let notificationService: NotificationService
|
|
||||||
let settingService: SettingService
|
|
||||||
let instanceService: InstanceService
|
|
||||||
|
|
||||||
let blockDomainService: BlockDomainService
|
public let placeholderImageCacheService = PlaceholderImageCacheService()
|
||||||
let statusFilterService: StatusFilterService
|
public let blurhashImageCacheService = BlurhashImageCacheService.shared
|
||||||
let photoLibraryService = PhotoLibraryService()
|
|
||||||
|
|
||||||
let placeholderImageCacheService = PlaceholderImageCacheService()
|
public let documentStore: DocumentStore
|
||||||
let blurhashImageCacheService = BlurhashImageCacheService.shared
|
|
||||||
|
|
||||||
let documentStore: DocumentStore
|
|
||||||
private var documentStoreSubscription: AnyCancellable!
|
private var documentStoreSubscription: AnyCancellable!
|
||||||
|
|
||||||
let overrideTraitCollection = CurrentValueSubject<UITraitCollection?, Never>(nil)
|
let overrideTraitCollection = CurrentValueSubject<UITraitCollection?, Never>(nil)
|
||||||
|
@ -47,7 +45,7 @@ class AppContext: ObservableObject {
|
||||||
.share()
|
.share()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
init() {
|
public init() {
|
||||||
let _coreDataStack = CoreDataStack()
|
let _coreDataStack = CoreDataStack()
|
||||||
let _managedObjectContext = _coreDataStack.persistentContainer.viewContext
|
let _managedObjectContext = _coreDataStack.persistentContainer.viewContext
|
||||||
let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext()
|
let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext()
|
|
@ -9,8 +9,8 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import KeychainAccess
|
import KeychainAccess
|
||||||
import Keys
|
|
||||||
import MastodonCommon
|
import MastodonCommon
|
||||||
|
import ArkanaKeys
|
||||||
|
|
||||||
public final class AppSecret {
|
public final class AppSecret {
|
||||||
|
|
||||||
|
@ -36,12 +36,10 @@ public final class AppSecret {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let keys = MastodonKeys()
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
self.notificationEndpoint = keys.notification_endpoint_debug
|
self.notificationEndpoint = Keys.Debug().notificationEndpoint
|
||||||
#else
|
#else
|
||||||
self.notificationEndpoint = keys.notification_endpoint
|
self.notificationEndpoint = Keys.Release().notificationEndpoint
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// MastodonAuthenticationBox.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreDataStack
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
public struct MastodonAuthenticationBox: UserIdentifier {
|
||||||
|
public let authenticationRecord: ManagedObjectRecord<MastodonAuthentication>
|
||||||
|
public let domain: String
|
||||||
|
public let userID: MastodonUser.ID
|
||||||
|
public let appAuthorization: Mastodon.API.OAuth.Authorization
|
||||||
|
public let userAuthorization: Mastodon.API.OAuth.Authorization
|
||||||
|
|
||||||
|
public init(
|
||||||
|
authenticationRecord: ManagedObjectRecord<MastodonAuthentication>,
|
||||||
|
domain: String,
|
||||||
|
userID: MastodonUser.ID,
|
||||||
|
appAuthorization: Mastodon.API.OAuth.Authorization,
|
||||||
|
userAuthorization: Mastodon.API.OAuth.Authorization
|
||||||
|
) {
|
||||||
|
self.authenticationRecord = authenticationRecord
|
||||||
|
self.domain = domain
|
||||||
|
self.userID = userID
|
||||||
|
self.appAuthorization = appAuthorization
|
||||||
|
self.userAuthorization = userAuthorization
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// DocumentStore.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-1-27.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
public class DocumentStore: ObservableObject {
|
||||||
|
public let appStartUpTimestamp = Date()
|
||||||
|
public var defaultRevealStatusDict: [Mastodon.Entity.Status.ID: Bool] = [:]
|
||||||
|
}
|
|
@ -29,3 +29,15 @@ extension Collection where Element == Mastodon.Entity.Emoji {
|
||||||
return dictionary
|
return dictionary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MastodonEmoji {
|
||||||
|
public convenience init(emoji: Mastodon.Entity.Emoji) {
|
||||||
|
self.init(
|
||||||
|
code: emoji.shortcode,
|
||||||
|
url: emoji.url,
|
||||||
|
staticURL: emoji.staticURL,
|
||||||
|
visibleInPicker: emoji.visibleInPicker,
|
||||||
|
category: emoji.category
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
//
|
//
|
||||||
// MastodonUser.swift
|
// MastodonUser.swift
|
||||||
|
// Mastodon
|
||||||
//
|
//
|
||||||
//
|
// Created by MainasuK Cirno on 2021/2/3.
|
||||||
// Created by MainasuK on 2022-4-14.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonCommon
|
import MastodonSDK
|
||||||
|
|
||||||
extension MastodonUser {
|
extension MastodonUser {
|
||||||
|
|
||||||
|
@ -55,3 +55,21 @@ extension MastodonUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MastodonUser {
|
||||||
|
|
||||||
|
public var profileURL: URL {
|
||||||
|
if let urlString = self.url,
|
||||||
|
let url = URL(string: urlString) {
|
||||||
|
return url
|
||||||
|
} else {
|
||||||
|
return URL(string: "https://\(self.domain)/@\(username)")!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var activityItems: [Any] {
|
||||||
|
var items: [Any] = []
|
||||||
|
items.append(profileURL)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
final class SettingFetchedResultController: NSObject {
|
public final class SettingFetchedResultController: NSObject {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ final class SettingFetchedResultController: NSObject {
|
||||||
// input
|
// input
|
||||||
|
|
||||||
// output
|
// output
|
||||||
let settings = CurrentValueSubject<[Setting], Never>([])
|
public let settings = CurrentValueSubject<[Setting], Never>([])
|
||||||
|
|
||||||
init(managedObjectContext: NSManagedObjectContext, additionalPredicate: NSPredicate?) {
|
public init(managedObjectContext: NSManagedObjectContext, additionalPredicate: NSPredicate?) {
|
||||||
self.fetchedResultsController = {
|
self.fetchedResultsController = {
|
||||||
let fetchRequest = Setting.sortedFetchRequest
|
let fetchRequest = Setting.sortedFetchRequest
|
||||||
fetchRequest.returnsObjectsAsFaults = false
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
|
@ -55,7 +55,7 @@ final class SettingFetchedResultController: NSObject {
|
||||||
|
|
||||||
// MARK: - NSFetchedResultsControllerDelegate
|
// MARK: - NSFetchedResultsControllerDelegate
|
||||||
extension SettingFetchedResultController: NSFetchedResultsControllerDelegate {
|
extension SettingFetchedResultController: NSFetchedResultsControllerDelegate {
|
||||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
|
||||||
let objects = fetchedResultsController.fetchedObjects ?? []
|
let objects = fetchedResultsController.fetchedObjects ?? []
|
|
@ -11,7 +11,6 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonUI
|
|
||||||
|
|
||||||
final class StatusFetchedResultsController: NSObject {
|
final class StatusFetchedResultsController: NSObject {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonUI
|
|
||||||
|
|
||||||
final class UserFetchedResultsController: NSObject {
|
final class UserFetchedResultsController: NSObject {
|
||||||
|
|
|
@ -19,7 +19,7 @@ extension Persistence.MastodonUser {
|
||||||
public let entity: Mastodon.Entity.Account
|
public let entity: Mastodon.Entity.Account
|
||||||
public let cache: Persistence.PersistCache<MastodonUser>?
|
public let cache: Persistence.PersistCache<MastodonUser>?
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "MastodonUser", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
domain: String,
|
domain: String,
|
||||||
|
@ -127,7 +127,7 @@ extension Persistence.MastodonUser {
|
||||||
public let entity: Mastodon.Entity.Relationship
|
public let entity: Mastodon.Entity.Relationship
|
||||||
public let me: MastodonUser
|
public let me: MastodonUser
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "MastodonUser", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
entity: Mastodon.Entity.Relationship,
|
entity: Mastodon.Entity.Relationship,
|
|
@ -19,7 +19,7 @@ extension Persistence.Notification {
|
||||||
public let entity: Mastodon.Entity.Notification
|
public let entity: Mastodon.Entity.Notification
|
||||||
public let me: MastodonUser
|
public let me: MastodonUser
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "Notification", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
domain: String,
|
domain: String,
|
|
@ -18,8 +18,7 @@ extension Persistence.Poll {
|
||||||
public let entity: Mastodon.Entity.Poll
|
public let entity: Mastodon.Entity.Poll
|
||||||
public let me: MastodonUser?
|
public let me: MastodonUser?
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "Poll", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
domain: String,
|
domain: String,
|
||||||
entity: Mastodon.Entity.Poll,
|
entity: Mastodon.Entity.Poll,
|
|
@ -18,7 +18,7 @@ extension Persistence.PollOption {
|
||||||
public let entity: Mastodon.Entity.Poll.Option
|
public let entity: Mastodon.Entity.Poll.Option
|
||||||
public let me: MastodonUser?
|
public let me: MastodonUser?
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "PollOption", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
index: Int,
|
index: Int,
|
|
@ -17,8 +17,7 @@ extension Persistence.SearchHistory {
|
||||||
public let entity: Entity
|
public let entity: Entity
|
||||||
public let me: MastodonUser
|
public let me: MastodonUser
|
||||||
public let now: Date
|
public let now: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "SearchHistory", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
me: MastodonUser,
|
me: MastodonUser,
|
|
@ -21,7 +21,7 @@ extension Persistence.Status {
|
||||||
public let statusCache: Persistence.PersistCache<Status>?
|
public let statusCache: Persistence.PersistCache<Status>?
|
||||||
public let userCache: Persistence.PersistCache<MastodonUser>?
|
public let userCache: Persistence.PersistCache<MastodonUser>?
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "Status", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
domain: String,
|
domain: String,
|
|
@ -18,7 +18,7 @@ extension Persistence.Tag {
|
||||||
public let entity: Mastodon.Entity.Tag
|
public let entity: Mastodon.Entity.Tag
|
||||||
public let me: MastodonUser?
|
public let me: MastodonUser?
|
||||||
public let networkDate: Date
|
public let networkDate: Date
|
||||||
public let log = OSLog.api
|
public let log = Logger(subsystem: "Tag", category: "Persistence")
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
domain: String,
|
domain: String,
|
|
@ -10,12 +10,12 @@ import MastodonSDK
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
||||||
enum APIError: Error {
|
public enum APIError: Error {
|
||||||
|
|
||||||
case implicit(ErrorReason)
|
case implicit(ErrorReason)
|
||||||
case explicit(ErrorReason)
|
case explicit(ErrorReason)
|
||||||
|
|
||||||
enum ErrorReason {
|
public enum ErrorReason {
|
||||||
// application internal error
|
// application internal error
|
||||||
case authenticationMissing
|
case authenticationMissing
|
||||||
case badRequest
|
case badRequest
|
||||||
|
@ -60,7 +60,7 @@ extension APIService.APIError: LocalizedError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var failureReason: String? {
|
public var failureReason: String? {
|
||||||
switch errorReason {
|
switch errorReason {
|
||||||
case .authenticationMissing: return "Account credential not found."
|
case .authenticationMissing: return "Account credential not found."
|
||||||
case .badRequest: return "Request invalid."
|
case .badRequest: return "Request invalid."
|
||||||
|
@ -75,7 +75,7 @@ extension APIService.APIError: LocalizedError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var helpAnchor: String? {
|
public var helpAnchor: String? {
|
||||||
switch errorReason {
|
switch errorReason {
|
||||||
case .authenticationMissing: return "Please request after authenticated."
|
case .authenticationMissing: return "Please request after authenticated."
|
||||||
case .badRequest: return L10n.Common.Alerts.Common.pleaseTryAgain
|
case .badRequest: return L10n.Common.Alerts.Common.pleaseTryAgain
|
|
@ -9,6 +9,7 @@ import os.log
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
|
import MastodonCommon
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
||||||
|
@ -59,7 +60,7 @@ extension APIService {
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
)
|
)
|
||||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||||
let log = OSLog.api
|
let logger = Logger(subsystem: "Account", category: "API")
|
||||||
let account = response.value
|
let account = response.value
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
let managedObjectContext = self.backgroundManagedObjectContext
|
||||||
|
@ -74,7 +75,7 @@ extension APIService {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
let flag = result.isNewInsertion ? "+" : "-"
|
let flag = result.isNewInsertion ? "+" : "-"
|
||||||
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: mastodon user [%s](%s)%s verifed", ((#file as NSString).lastPathComponent), #line, #function, flag, result.user.id, result.user.username)
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): mastodon user [\(flag)](\(result.user.id))\(result.user.username) verifed")
|
||||||
}
|
}
|
||||||
.setFailureType(to: Error.self)
|
.setFailureType(to: Error.self)
|
||||||
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Account> in
|
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Account> in
|
||||||
|
@ -95,7 +96,7 @@ extension APIService {
|
||||||
query: Mastodon.API.Account.UpdateCredentialQuery,
|
query: Mastodon.API.Account.UpdateCredentialQuery,
|
||||||
authorization: Mastodon.API.OAuth.Authorization
|
authorization: Mastodon.API.OAuth.Authorization
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
||||||
let logger = Logger(subsystem: "APIService", category: "Account")
|
let logger = Logger(subsystem: "Account", category: "API")
|
||||||
|
|
||||||
let response = try await Mastodon.API.Account.updateCredentials(
|
let response = try await Mastodon.API.Account.updateCredentials(
|
||||||
session: session,
|
session: session,
|
|
@ -24,7 +24,7 @@ extension APIService {
|
||||||
func createApplication(domain: String) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error> {
|
func createApplication(domain: String) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error> {
|
||||||
let query = Mastodon.API.App.CreateQuery(
|
let query = Mastodon.API.App.CreateQuery(
|
||||||
clientName: APIService.clientName,
|
clientName: APIService.clientName,
|
||||||
redirectURIs: MastodonAuthenticationController.callbackURL,
|
redirectURIs: APIService.oauthCallbackURL,
|
||||||
website: APIService.appWebsite
|
website: APIService.appWebsite
|
||||||
)
|
)
|
||||||
return Mastodon.API.App.create(
|
return Mastodon.API.App.create(
|
|
@ -10,7 +10,6 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
import DateToolsSwift
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
|
@ -9,7 +9,6 @@ import Combine
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import DateToolsSwift
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
|
@ -10,7 +10,6 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
import DateToolsSwift
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
|
@ -10,7 +10,6 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
import DateToolsSwift
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
|
@ -10,7 +10,6 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import CommonOSLog
|
import CommonOSLog
|
||||||
import DateToolsSwift
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue