chore: [WIP] inject AuthContext into ViewModel
This commit is contained in:
parent
f73241caee
commit
bb5c999bea
|
@ -7,6 +7,5 @@ set -eo pipefail
|
||||||
|
|
||||||
xcodebuild -workspace Mastodon.xcworkspace \
|
xcodebuild -workspace Mastodon.xcworkspace \
|
||||||
-scheme Mastodon \
|
-scheme Mastodon \
|
||||||
-destination "platform=iOS Simulator,name=iPhone SE (2nd generation)" \
|
|
||||||
clean \
|
clean \
|
||||||
build | xcpretty
|
build | xcpretty
|
||||||
|
|
|
@ -4109,7 +4109,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.7;
|
MARKETING_VERSION = 1.4.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -4139,7 +4139,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.7;
|
MARKETING_VERSION = 1.4.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -4312,7 +4312,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.7;
|
MARKETING_VERSION = 1.4.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -4609,7 +4609,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.7;
|
MARKETING_VERSION = 1.4.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|
|
@ -112,12 +112,12 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>7</integer>
|
<integer>25</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>6</integer>
|
<integer>24</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -1,241 +1,239 @@
|
||||||
{
|
{
|
||||||
"object": {
|
"pins" : [
|
||||||
"pins": [
|
{
|
||||||
{
|
"identity" : "alamofire",
|
||||||
"package": "Alamofire",
|
"kind" : "remoteSourceControl",
|
||||||
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
"location" : "https://github.com/Alamofire/Alamofire.git",
|
||||||
"state": {
|
"state" : {
|
||||||
"branch": null,
|
"revision" : "354dda32d89fc8cd4f5c46487f64957d355f53d8",
|
||||||
"revision": "354dda32d89fc8cd4f5c46487f64957d355f53d8",
|
"version" : "5.6.1"
|
||||||
"version": "5.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "e7f9fd4681ae41bf6f3056db08af4f401d61da52",
|
|
||||||
"version": "1.0.16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "0ea7545b5c918285aacc044dc75048625c8257cc",
|
|
||||||
"version": "10.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "34ecb6e7c4e0e07494960ab2f7cc9a02293915a6",
|
|
||||||
"version": "3.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "2e63d0061da449ad0ed130768d05dceb1496de44",
|
|
||||||
"version": "5.12.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886",
|
|
||||||
"version": "2.4.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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
|
"identity" : "alamofireimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/Alamofire/AlamofireImage.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10",
|
||||||
|
"version" : "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "commonoslog",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/MainasuK/CommonOSLog",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2",
|
||||||
|
"version" : "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "faviconfinder",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/will-lumley/FaviconFinder.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a",
|
||||||
|
"version" : "3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "flanimatedimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/Flipboard/FLAnimatedImage.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "e7f9fd4681ae41bf6f3056db08af4f401d61da52",
|
||||||
|
"version" : "1.0.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "fpsindicator",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/MainasuK/FPSIndicator.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796",
|
||||||
|
"version" : "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "fuzi",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/cezheng/Fuzi.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "f08c8323da21e985f3772610753bcfc652c2103f",
|
||||||
|
"version" : "3.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "keychainaccess",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||||
|
"version" : "4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "metatextkit",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TwidereProject/MetaTextKit.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "dcd5255d6930c2fab408dc8562c577547e477624",
|
||||||
|
"version" : "2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "nuke",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kean/Nuke.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "0ea7545b5c918285aacc044dc75048625c8257cc",
|
||||||
|
"version" : "10.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "nuke-flanimatedimage-plugin",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16",
|
||||||
|
"version" : "8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "pageboy",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/uias/Pageboy",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "34ecb6e7c4e0e07494960ab2f7cc9a02293915a6",
|
||||||
|
"version" : "3.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "panmodal",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/slackhq/PanModal.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b012aecb6b67a8e46369227f893c12544846613f",
|
||||||
|
"version" : "1.2.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sdwebimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SDWebImage/SDWebImage.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2e63d0061da449ad0ed130768d05dceb1496de44",
|
||||||
|
"version" : "5.12.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-collections",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-collections.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "f504716c27d2e5d4144fa4794b12129301d17729",
|
||||||
|
"version" : "1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-nio",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-nio.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef",
|
||||||
|
"version" : "1.14.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-nio-zlib-support",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-nio-zlib-support.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd",
|
||||||
|
"version" : "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftsoup",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886",
|
||||||
|
"version" : "2.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftui-introspect",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "f2616860a41f9d9932da412a8978fec79c06fe24",
|
||||||
|
"version" : "0.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftyjson",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SwiftyJSON/SwiftyJSON.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
|
||||||
|
"version" : "5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tabbarpager",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TwidereProject/TabBarPager.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "488aa66d157a648901b61721212c0dec23d27ee5",
|
||||||
|
"version" : "0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tabman",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/uias/Tabman",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465",
|
||||||
|
"version" : "2.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "thirdpartymailer",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/vtourraine/ThirdPartyMailer.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "44c1cfaa6969963f22691aa67f88a69e3b6d651f",
|
||||||
|
"version" : "2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tocropviewcontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TimOliver/TOCropViewController.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e",
|
||||||
|
"version" : "2.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "uihostingconfigurationbackport",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3",
|
||||||
|
"version" : "0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "uitextview-placeholder",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/MainasuK/UITextView-Placeholder.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "20f513ded04a040cdf5467f0891849b1763ede3b",
|
||||||
|
"version" : "1.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ final public class SceneCoordinator {
|
||||||
private weak var sceneDelegate: SceneDelegate!
|
private weak var sceneDelegate: SceneDelegate!
|
||||||
private weak var appContext: AppContext!
|
private weak var appContext: AppContext!
|
||||||
|
|
||||||
private var authContext: AuthContext?
|
private(set) var authContext: AuthContext?
|
||||||
|
|
||||||
let id = UUID().uuidString
|
let id = UUID().uuidString
|
||||||
|
|
||||||
|
@ -45,100 +45,83 @@ final public class SceneCoordinator {
|
||||||
|
|
||||||
appContext.notificationService.requestRevealNotificationPublisher
|
appContext.notificationService.requestRevealNotificationPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.compactMap { [weak self] pushNotification -> AnyPublisher<MastodonPushNotification?, Never> in
|
.sink(receiveValue: { [weak self] pushNotification in
|
||||||
guard let self = self else { return Just(nil).eraseToAnyPublisher() }
|
|
||||||
// skip if no available account
|
|
||||||
guard let currentActiveAuthenticationBox = appContext.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
return Just(nil).eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
|
|
||||||
let accessToken = pushNotification.accessToken // use raw accessToken value without normalize
|
|
||||||
if currentActiveAuthenticationBox.userAuthorization.accessToken == accessToken {
|
|
||||||
// do nothing if notification for current account
|
|
||||||
return Just(pushNotification).eraseToAnyPublisher()
|
|
||||||
} else {
|
|
||||||
// switch to notification's account
|
|
||||||
let request = MastodonAuthentication.sortedFetchRequest
|
|
||||||
request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken)
|
|
||||||
request.returnsObjectsAsFaults = false
|
|
||||||
request.fetchLimit = 1
|
|
||||||
do {
|
|
||||||
guard let authentication = try appContext.managedObjectContext.fetch(request).first else {
|
|
||||||
return Just(nil).eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
let domain = authentication.domain
|
|
||||||
let userID = authentication.userID
|
|
||||||
return appContext.authenticationService.activeMastodonUser(domain: domain, userID: userID)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.map { [weak self] result -> MastodonPushNotification? in
|
|
||||||
guard let self = self else { return nil }
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
// reset view hierarchy
|
|
||||||
self.setup()
|
|
||||||
return pushNotification
|
|
||||||
case .failure:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.delay(for: 1, scheduler: DispatchQueue.main) // set delay to slow transition (not must)
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
} catch {
|
|
||||||
assertionFailure(error.localizedDescription)
|
|
||||||
return Just(nil).eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.switchToLatest()
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] pushNotification in
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let pushNotification = pushNotification else { return }
|
Task {
|
||||||
|
guard let currentActiveAuthenticationBox = self.authContext?.mastodonAuthenticationBox else { return }
|
||||||
// redirect to notification tab
|
let accessToken = pushNotification.accessToken // use raw accessToken value without normalize
|
||||||
self.switchToTabBar(tab: .notification)
|
if currentActiveAuthenticationBox.userAuthorization.accessToken == accessToken {
|
||||||
|
// do nothing if notification for current account
|
||||||
|
return
|
||||||
// Delay in next run loop
|
} else {
|
||||||
DispatchQueue.main.async { [weak self] in
|
// switch to notification's account
|
||||||
guard let self = self else { return }
|
let request = MastodonAuthentication.sortedFetchRequest
|
||||||
|
request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken)
|
||||||
// Note:
|
request.returnsObjectsAsFaults = false
|
||||||
// show (push) on phone and pad
|
request.fetchLimit = 1
|
||||||
let from: UIViewController? = {
|
do {
|
||||||
if let splitViewController = self.splitViewController {
|
guard let authentication = try appContext.managedObjectContext.fetch(request).first else {
|
||||||
if splitViewController.compactMainTabBarViewController.topMost?.view.window != nil {
|
return
|
||||||
// compact
|
|
||||||
return splitViewController.compactMainTabBarViewController.topMost
|
|
||||||
} else {
|
|
||||||
// expand
|
|
||||||
return splitViewController.contentSplitViewController.mainTabBarController.topMost
|
|
||||||
}
|
}
|
||||||
} else {
|
let domain = authentication.domain
|
||||||
return self.tabBarController.topMost
|
let userID = authentication.userID
|
||||||
|
let isSuccess = try await appContext.authenticationService.activeMastodonUser(domain: domain, userID: userID)
|
||||||
|
guard isSuccess else { return }
|
||||||
|
|
||||||
|
self.setup()
|
||||||
|
try await Task.sleep(nanoseconds: .second * 1)
|
||||||
|
|
||||||
|
// redirect to notification tab
|
||||||
|
self.switchToTabBar(tab: .notification)
|
||||||
|
|
||||||
|
// Delay in next run loop
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
// Note:
|
||||||
|
// show (push) on phone and pad
|
||||||
|
let from: UIViewController? = {
|
||||||
|
if let splitViewController = self.splitViewController {
|
||||||
|
if splitViewController.compactMainTabBarViewController.topMost?.view.window != nil {
|
||||||
|
// compact
|
||||||
|
return splitViewController.compactMainTabBarViewController.topMost
|
||||||
|
} else {
|
||||||
|
// expand
|
||||||
|
return splitViewController.contentSplitViewController.mainTabBarController.topMost
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return self.tabBarController.topMost
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// show notification related content
|
||||||
|
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
|
||||||
|
guard let authContext = self.authContext else { return }
|
||||||
|
let notificationID = String(pushNotification.notificationID)
|
||||||
|
|
||||||
|
switch type {
|
||||||
|
case .follow:
|
||||||
|
let profileViewModel = RemoteProfileViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||||
|
_ = self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||||
|
case .followRequest:
|
||||||
|
// do nothing
|
||||||
|
break
|
||||||
|
case .mention, .reblog, .favourite, .poll, .status:
|
||||||
|
let threadViewModel = RemoteThreadViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||||
|
_ = self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||||
|
case ._other:
|
||||||
|
assertionFailure()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} // end DispatchQueue.main.async
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
assertionFailure(error.localizedDescription)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
// show notification related content
|
|
||||||
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
|
|
||||||
let notificationID = String(pushNotification.notificationID)
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .follow:
|
|
||||||
let profileViewModel = RemoteProfileViewModel(context: appContext, notificationID: notificationID)
|
|
||||||
self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
|
||||||
case .followRequest:
|
|
||||||
// do nothing
|
|
||||||
break
|
|
||||||
case .mention, .reblog, .favourite, .poll, .status:
|
|
||||||
let threadViewModel = RemoteThreadViewModel(context: appContext, notificationID: notificationID)
|
|
||||||
self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
|
||||||
case ._other:
|
|
||||||
assertionFailure()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} // end DispatchQueue.main.async
|
} // end Task
|
||||||
}
|
})
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +163,7 @@ extension SceneCoordinator {
|
||||||
case hashtagTimeline(viewModel: HashtagTimelineViewModel)
|
case hashtagTimeline(viewModel: HashtagTimelineViewModel)
|
||||||
|
|
||||||
// profile
|
// profile
|
||||||
case accountList
|
case accountList(viewModel: AccountListViewModel)
|
||||||
case profile(viewModel: ProfileViewModel)
|
case profile(viewModel: ProfileViewModel)
|
||||||
case favorite(viewModel: FavoriteViewModel)
|
case favorite(viewModel: FavoriteViewModel)
|
||||||
case follower(viewModel: FollowerListViewModel)
|
case follower(viewModel: FollowerListViewModel)
|
||||||
|
@ -260,6 +243,19 @@ extension SceneCoordinator {
|
||||||
transition: .modal(animated: true, completion: nil)
|
transition: .modal(animated: true, completion: nil)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let wizardViewController = WizardViewController()
|
||||||
|
if !wizardViewController.items.isEmpty,
|
||||||
|
let delegate = rootViewController as? WizardViewControllerDelegate
|
||||||
|
{
|
||||||
|
// do not add as child view controller.
|
||||||
|
// otherwise, the tab bar controller will add as a new tab
|
||||||
|
wizardViewController.delegate = delegate
|
||||||
|
wizardViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
wizardViewController.view.frame = rootViewController.view.bounds
|
||||||
|
rootViewController.view.addSubview(wizardViewController.view)
|
||||||
|
self.wizardViewController = wizardViewController
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -431,8 +427,9 @@ private extension SceneCoordinator {
|
||||||
let _viewController = HashtagTimelineViewController()
|
let _viewController = HashtagTimelineViewController()
|
||||||
_viewController.viewModel = viewModel
|
_viewController.viewModel = viewModel
|
||||||
viewController = _viewController
|
viewController = _viewController
|
||||||
case .accountList:
|
case .accountList(let viewModel):
|
||||||
let _viewController = AccountListViewController()
|
let _viewController = AccountListViewController()
|
||||||
|
_viewController.viewModel = viewModel
|
||||||
viewController = _viewController
|
viewController = _viewController
|
||||||
case .profile(let viewModel):
|
case .profile(let viewModel):
|
||||||
let _viewController = ProfileViewController()
|
let _viewController = ProfileViewController()
|
||||||
|
|
|
@ -10,4 +10,3 @@ import Foundation
|
||||||
enum ComposeStatusAttachmentSection: Hashable {
|
enum ComposeStatusAttachmentSection: Hashable {
|
||||||
case main
|
case main
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,16 @@ extension DiscoverySection {
|
||||||
static let logger = Logger(subsystem: "DiscoverySection", category: "logic")
|
static let logger = Logger(subsystem: "DiscoverySection", category: "logic")
|
||||||
|
|
||||||
class Configuration {
|
class Configuration {
|
||||||
|
let authContext: AuthContext
|
||||||
weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate?
|
weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate?
|
||||||
let familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher?
|
let familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
authContext: AuthContext,
|
||||||
profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil,
|
profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil,
|
||||||
familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher? = nil
|
familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher? = nil
|
||||||
) {
|
) {
|
||||||
|
self.authContext = authContext
|
||||||
self.profileCardTableViewCellDelegate = profileCardTableViewCellDelegate
|
self.profileCardTableViewCellDelegate = profileCardTableViewCellDelegate
|
||||||
self.familiarFollowers = familiarFollowers
|
self.familiarFollowers = familiarFollowers
|
||||||
}
|
}
|
||||||
|
@ -73,11 +76,9 @@ extension DiscoverySection {
|
||||||
} else {
|
} else {
|
||||||
cell.profileCardView.viewModel.familiarFollowers = nil
|
cell.profileCardView.viewModel.familiarFollowers = nil
|
||||||
}
|
}
|
||||||
|
// bind me
|
||||||
|
cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||||
}
|
}
|
||||||
context.authenticationService.activeMastodonAuthentication
|
|
||||||
.map { $0?.user }
|
|
||||||
.assign(to: \.me, on: cell.profileCardView.viewModel.relationshipViewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
return cell
|
return cell
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||||
|
|
|
@ -24,6 +24,7 @@ enum NotificationSection: Equatable, Hashable {
|
||||||
extension NotificationSection {
|
extension NotificationSection {
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
|
let authContext: AuthContext
|
||||||
weak var notificationTableViewCellDelegate: NotificationTableViewCellDelegate?
|
weak var notificationTableViewCellDelegate: NotificationTableViewCellDelegate?
|
||||||
let filterContext: Mastodon.Entity.Filter.Context?
|
let filterContext: Mastodon.Entity.Filter.Context?
|
||||||
let activeFilters: Published<[Mastodon.Entity.Filter]>.Publisher?
|
let activeFilters: Published<[Mastodon.Entity.Filter]>.Publisher?
|
||||||
|
@ -74,21 +75,20 @@ extension NotificationSection {
|
||||||
viewModel: NotificationTableViewCell.ViewModel,
|
viewModel: NotificationTableViewCell.ViewModel,
|
||||||
configuration: Configuration
|
configuration: Configuration
|
||||||
) {
|
) {
|
||||||
|
cell.notificationView.viewModel.authContext = configuration.authContext
|
||||||
|
|
||||||
StatusSection.setupStatusPollDataSource(
|
StatusSection.setupStatusPollDataSource(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: configuration.authContext,
|
||||||
statusView: cell.notificationView.statusView
|
statusView: cell.notificationView.statusView
|
||||||
)
|
)
|
||||||
|
|
||||||
StatusSection.setupStatusPollDataSource(
|
StatusSection.setupStatusPollDataSource(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: configuration.authContext,
|
||||||
statusView: cell.notificationView.quoteStatusView
|
statusView: cell.notificationView.quoteStatusView
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.notificationView.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
cell.configure(
|
cell.configure(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
|
|
|
@ -133,6 +133,7 @@ enum RecommendAccountSection: Equatable, Hashable {
|
||||||
extension RecommendAccountSection {
|
extension RecommendAccountSection {
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
|
let authContext: AuthContext
|
||||||
weak var suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate?
|
weak var suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,10 +151,7 @@ extension RecommendAccountSection {
|
||||||
cell.configure(user: user)
|
cell.configure(user: user)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
cell.viewModel.userIdentifier = configuration.authContext.mastodonAuthenticationBox
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
cell.delegate = configuration.suggestionAccountTableViewCellDelegate
|
cell.delegate = configuration.suggestionAccountTableViewCellDelegate
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
|
|
|
@ -23,6 +23,7 @@ enum ReportSection: Equatable, Hashable {
|
||||||
extension ReportSection {
|
extension ReportSection {
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
|
let authContext: AuthContext
|
||||||
}
|
}
|
||||||
|
|
||||||
static func diffableDataSource(
|
static func diffableDataSource(
|
||||||
|
@ -101,13 +102,11 @@ extension ReportSection {
|
||||||
) {
|
) {
|
||||||
StatusSection.setupStatusPollDataSource(
|
StatusSection.setupStatusPollDataSource(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: configuration.authContext,
|
||||||
statusView: cell.statusView
|
statusView: cell.statusView
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
cell.statusView.viewModel.authContext = configuration.authContext
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
cell.configure(
|
cell.configure(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
|
|
|
@ -25,6 +25,7 @@ extension SearchResultSection {
|
||||||
static let logger = Logger(subsystem: "SearchResultSection", category: "logic")
|
static let logger = Logger(subsystem: "SearchResultSection", category: "logic")
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
|
let authContext: AuthContext
|
||||||
weak var statusViewTableViewCellDelegate: StatusTableViewCellDelegate?
|
weak var statusViewTableViewCellDelegate: StatusTableViewCellDelegate?
|
||||||
weak var userTableViewCellDelegate: UserTableViewCellDelegate?
|
weak var userTableViewCellDelegate: UserTableViewCellDelegate?
|
||||||
}
|
}
|
||||||
|
@ -99,13 +100,11 @@ extension SearchResultSection {
|
||||||
) {
|
) {
|
||||||
StatusSection.setupStatusPollDataSource(
|
StatusSection.setupStatusPollDataSource(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: configuration.authContext,
|
||||||
statusView: cell.statusView
|
statusView: cell.statusView
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
cell.statusView.viewModel.authContext = configuration.authContext
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
cell.configure(
|
cell.configure(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
|
@ -120,7 +119,7 @@ extension SearchResultSection {
|
||||||
cell: UserTableViewCell,
|
cell: UserTableViewCell,
|
||||||
viewModel: UserTableViewCell.ViewModel,
|
viewModel: UserTableViewCell.ViewModel,
|
||||||
configuration: Configuration
|
configuration: Configuration
|
||||||
) {
|
) {
|
||||||
cell.configure(
|
cell.configure(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
|
|
|
@ -27,6 +27,7 @@ extension StatusSection {
|
||||||
static let logger = Logger(subsystem: "StatusSection", category: "logic")
|
static let logger = Logger(subsystem: "StatusSection", category: "logic")
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
|
let authContext: AuthContext
|
||||||
weak var statusTableViewCellDelegate: StatusTableViewCellDelegate?
|
weak var statusTableViewCellDelegate: StatusTableViewCellDelegate?
|
||||||
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||||
let filterContext: Mastodon.Entity.Filter.Context?
|
let filterContext: Mastodon.Entity.Filter.Context?
|
||||||
|
@ -159,6 +160,7 @@ extension StatusSection {
|
||||||
|
|
||||||
public static func setupStatusPollDataSource(
|
public static func setupStatusPollDataSource(
|
||||||
context: AppContext,
|
context: AppContext,
|
||||||
|
authContext: AuthContext,
|
||||||
statusView: StatusView
|
statusView: StatusView
|
||||||
) {
|
) {
|
||||||
let managedObjectContext = context.managedObjectContext
|
let managedObjectContext = context.managedObjectContext
|
||||||
|
@ -172,10 +174,7 @@ extension StatusSection {
|
||||||
return _cell ?? PollOptionTableViewCell()
|
return _cell ?? PollOptionTableViewCell()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
cell.pollOptionView.viewModel.authContext = authContext
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.pollOptionView.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
managedObjectContext.performAndWait {
|
managedObjectContext.performAndWait {
|
||||||
guard let option = record.object(in: managedObjectContext) else {
|
guard let option = record.object(in: managedObjectContext) else {
|
||||||
|
@ -212,14 +211,13 @@ extension StatusSection {
|
||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if needsUpdatePoll, let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value
|
if needsUpdatePoll {
|
||||||
{
|
|
||||||
let pollRecord: ManagedObjectRecord<Poll> = .init(objectID: option.poll.objectID)
|
let pollRecord: ManagedObjectRecord<Poll> = .init(objectID: option.poll.objectID)
|
||||||
Task { [weak context] in
|
Task { [weak context] in
|
||||||
guard let context = context else { return }
|
guard let context = context else { return }
|
||||||
_ = try await context.apiService.poll(
|
_ = try await context.apiService.poll(
|
||||||
poll: pollRecord,
|
poll: pollRecord,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,13 +246,11 @@ extension StatusSection {
|
||||||
) {
|
) {
|
||||||
setupStatusPollDataSource(
|
setupStatusPollDataSource(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: configuration.authContext,
|
||||||
statusView: cell.statusView
|
statusView: cell.statusView
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
cell.statusView.viewModel.authContext = configuration.authContext
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
cell.configure(
|
cell.configure(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
|
@ -277,13 +273,11 @@ extension StatusSection {
|
||||||
) {
|
) {
|
||||||
setupStatusPollDataSource(
|
setupStatusPollDataSource(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: configuration.authContext,
|
||||||
statusView: cell.statusView
|
statusView: cell.statusView
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
cell.statusView.viewModel.authContext = configuration.authContext
|
||||||
.map { $0 as UserIdentifier? }
|
|
||||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
cell.configure(
|
cell.configure(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
|
|
|
@ -11,16 +11,15 @@ import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToUserBlockAction(
|
static func responseToUserBlockAction(
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
user: ManagedObjectRecord<MastodonUser>,
|
user: ManagedObjectRecord<MastodonUser>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
_ = try await dependency.context.apiService.toggleBlock(
|
_ = try await dependency.context.apiService.toggleBlock(
|
||||||
user: user,
|
user: user,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,16 +12,15 @@ import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
public static func responseToStatusBookmarkAction(
|
public static func responseToStatusBookmarkAction(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
_ = try await provider.context.apiService.bookmark(
|
_ = try await provider.context.apiService.bookmark(
|
||||||
record: status,
|
record: status,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: provider.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,16 +12,15 @@ import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
public static func responseToStatusFavoriteAction(
|
public static func responseToStatusFavoriteAction(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
_ = try await provider.context.apiService.favorite(
|
_ = try await provider.context.apiService.favorite(
|
||||||
record: status,
|
record: status,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: provider.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,26 +14,24 @@ import MastodonLocalization
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToUserFollowAction(
|
static func responseToUserFollowAction(
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
user: ManagedObjectRecord<MastodonUser>,
|
user: ManagedObjectRecord<MastodonUser>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
_ = try await dependency.context.apiService.toggleFollow(
|
_ = try await dependency.context.apiService.toggleFollow(
|
||||||
user: user,
|
user: user,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToUserFollowRequestAction(
|
static func responseToUserFollowRequestAction(
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
notification: ManagedObjectRecord<Notification>,
|
notification: ManagedObjectRecord<Notification>,
|
||||||
query: Mastodon.API.Account.FollowReqeustQuery,
|
query: Mastodon.API.Account.FollowReqeustQuery
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
@ -72,7 +70,7 @@ extension DataSourceFacade {
|
||||||
_ = try await dependency.context.apiService.followRequest(
|
_ = try await dependency.context.apiService.followRequest(
|
||||||
userID: userID,
|
userID: userID,
|
||||||
query: query,
|
query: query,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
// reset state when failure
|
// reset state when failure
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
@MainActor
|
@MainActor
|
||||||
static func coordinateToHashtagScene(
|
static func coordinateToHashtagScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
tag: DataSourceItem.TagKind
|
tag: DataSourceItem.TagKind
|
||||||
) async {
|
) async {
|
||||||
switch tag {
|
switch tag {
|
||||||
|
@ -25,11 +26,12 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static func coordinateToHashtagScene(
|
static func coordinateToHashtagScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
tag: Mastodon.Entity.Tag
|
tag: Mastodon.Entity.Tag
|
||||||
) async {
|
) async {
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
||||||
context: provider.context,
|
context: provider.context,
|
||||||
|
authContext: provider.authContext,
|
||||||
hashtag: tag.name
|
hashtag: tag.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static func coordinateToHashtagScene(
|
static func coordinateToHashtagScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
tag: ManagedObjectRecord<Tag>
|
tag: ManagedObjectRecord<Tag>
|
||||||
) async {
|
) async {
|
||||||
let managedObjectContext = provider.context.managedObjectContext
|
let managedObjectContext = provider.context.managedObjectContext
|
||||||
|
@ -55,6 +57,7 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
||||||
context: provider.context,
|
context: provider.context,
|
||||||
|
authContext: provider.authContext,
|
||||||
hashtag: name
|
hashtag: name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
|
|
||||||
static func responseToMetaTextAction(
|
static func responseToMetaTextAction(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
target: StatusTarget,
|
target: StatusTarget,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>,
|
||||||
meta: Meta
|
meta: Meta
|
||||||
|
@ -33,7 +34,7 @@ extension DataSourceFacade {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func responseToMetaTextAction(
|
static func responseToMetaTextAction(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>,
|
||||||
meta: Meta
|
meta: Meta
|
||||||
) async {
|
) async {
|
||||||
|
@ -47,19 +48,20 @@ extension DataSourceFacade {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain,
|
let domain = provider.authContext.mastodonAuthenticationBox.domain
|
||||||
|
if url.host == domain,
|
||||||
url.pathComponents.count >= 4,
|
url.pathComponents.count >= 4,
|
||||||
url.pathComponents[0] == "/",
|
url.pathComponents[0] == "/",
|
||||||
url.pathComponents[1] == "web",
|
url.pathComponents[1] == "web",
|
||||||
url.pathComponents[2] == "statuses" {
|
url.pathComponents[2] == "statuses" {
|
||||||
let statusID = url.pathComponents[3]
|
let statusID = url.pathComponents[3]
|
||||||
let threadViewModel = RemoteThreadViewModel(context: provider.context, statusID: statusID)
|
let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID)
|
||||||
await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
|
await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
|
||||||
} else {
|
} else {
|
||||||
await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
case .hashtag(_, let hashtag, _):
|
case .hashtag(_, let hashtag, _):
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, hashtag: hashtag)
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, authContext: provider.authContext, hashtag: hashtag)
|
||||||
await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
|
await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
|
||||||
case .mention(_, let mention, let userInfo):
|
case .mention(_, let mention, let userInfo):
|
||||||
await coordinateToProfileScene(
|
await coordinateToProfileScene(
|
||||||
|
|
|
@ -11,16 +11,15 @@ import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToUserMuteAction(
|
static func responseToUserMuteAction(
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
user: ManagedObjectRecord<MastodonUser>,
|
user: ManagedObjectRecord<MastodonUser>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
_ = try await dependency.context.apiService.toggleMute(
|
_ = try await dependency.context.apiService.toggleMute(
|
||||||
user: user,
|
user: user,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
|
|
||||||
static func coordinateToProfileScene(
|
static func coordinateToProfileScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
target: StatusTarget,
|
target: StatusTarget,
|
||||||
status: ManagedObjectRecord<Status>
|
status: ManagedObjectRecord<Status>
|
||||||
) async {
|
) async {
|
||||||
|
@ -32,7 +33,7 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static func coordinateToProfileScene(
|
static func coordinateToProfileScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
user: ManagedObjectRecord<MastodonUser>
|
user: ManagedObjectRecord<MastodonUser>
|
||||||
) async {
|
) async {
|
||||||
guard let user = user.object(in: provider.context.managedObjectContext) else {
|
guard let user = user.object(in: provider.context.managedObjectContext) else {
|
||||||
|
@ -42,6 +43,7 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
let profileViewModel = CachedProfileViewModel(
|
let profileViewModel = CachedProfileViewModel(
|
||||||
context: provider.context,
|
context: provider.context,
|
||||||
|
authContext: provider.authContext,
|
||||||
mastodonUser: user
|
mastodonUser: user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,13 +59,12 @@ extension DataSourceFacade {
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
|
|
||||||
static func coordinateToProfileScene(
|
static func coordinateToProfileScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>,
|
||||||
mention: String, // username,
|
mention: String, // username,
|
||||||
userInfo: [AnyHashable: Any]?
|
userInfo: [AnyHashable: Any]?
|
||||||
) async {
|
) async {
|
||||||
guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
let domain = provider.authContext.mastodonAuthenticationBox.domain
|
||||||
let domain = authenticationBox.domain
|
|
||||||
|
|
||||||
let href = userInfo?["href"] as? String
|
let href = userInfo?["href"] as? String
|
||||||
guard let url = href.flatMap({ URL(string: $0) }) else { return }
|
guard let url = href.flatMap({ URL(string: $0) }) else { return }
|
||||||
|
@ -85,8 +86,8 @@ extension DataSourceFacade {
|
||||||
let userID = mention.id
|
let userID = mention.id
|
||||||
let profileViewModel: ProfileViewModel = {
|
let profileViewModel: ProfileViewModel = {
|
||||||
// check if self
|
// check if self
|
||||||
guard userID != authenticationBox.userID else {
|
guard userID != provider.authContext.mastodonAuthenticationBox.userID else {
|
||||||
return MeProfileViewModel(context: provider.context)
|
return MeProfileViewModel(context: provider.context, authContext: provider.authContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = MastodonUser.sortedFetchRequest
|
let request = MastodonUser.sortedFetchRequest
|
||||||
|
@ -95,9 +96,9 @@ extension DataSourceFacade {
|
||||||
let _user = provider.context.managedObjectContext.safeFetch(request).first
|
let _user = provider.context.managedObjectContext.safeFetch(request).first
|
||||||
|
|
||||||
if let user = _user {
|
if let user = _user {
|
||||||
return CachedProfileViewModel(context: provider.context, mastodonUser: user)
|
return CachedProfileViewModel(context: provider.context, authContext: provider.authContext, mastodonUser: user)
|
||||||
} else {
|
} else {
|
||||||
return RemoteProfileViewModel(context: provider.context, userID: userID)
|
return RemoteProfileViewModel(context: provider.context, authContext: provider.authContext, userID: userID)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,15 @@ import MastodonUI
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToStatusReblogAction(
|
static func responseToStatusReblogAction(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
_ = try await provider.context.apiService.reblog(
|
_ = try await provider.context.apiService.reblog(
|
||||||
record: status,
|
record: status,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: provider.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,18 @@ import MastodonCore
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
|
|
||||||
static func responseToCreateSearchHistory(
|
static func responseToCreateSearchHistory(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
item: DataSourceItem
|
item: DataSourceItem
|
||||||
) async {
|
) async {
|
||||||
switch item {
|
switch item {
|
||||||
case .status:
|
case .status:
|
||||||
break // not create search history for status
|
break // not create search history for status
|
||||||
case .user(let record):
|
case .user(let record):
|
||||||
let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value
|
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||||
|
|
||||||
try? await managedObjectContext.performChanges {
|
try? await managedObjectContext.performChanges {
|
||||||
guard let me = authenticationBox?.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||||
guard let user = record.object(in: managedObjectContext) else { return }
|
guard let user = record.object(in: managedObjectContext) else { return }
|
||||||
_ = Persistence.SearchHistory.createOrMerge(
|
_ = Persistence.SearchHistory.createOrMerge(
|
||||||
in: managedObjectContext,
|
in: managedObjectContext,
|
||||||
|
@ -35,13 +35,12 @@ extension DataSourceFacade {
|
||||||
)
|
)
|
||||||
} // end try? await managedObjectContext.performChanges { … }
|
} // end try? await managedObjectContext.performChanges { … }
|
||||||
case .hashtag(let tag):
|
case .hashtag(let tag):
|
||||||
let _authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value
|
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case .entity(let entity):
|
case .entity(let entity):
|
||||||
try? await managedObjectContext.performChanges {
|
try? await managedObjectContext.performChanges {
|
||||||
guard let authenticationBox = _authenticationBox else { return }
|
|
||||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||||
|
|
||||||
let now = Date()
|
let now = Date()
|
||||||
|
@ -67,7 +66,7 @@ extension DataSourceFacade {
|
||||||
} // end try? await managedObjectContext.performChanges { … }
|
} // end try? await managedObjectContext.performChanges { … }
|
||||||
case .record(let record):
|
case .record(let record):
|
||||||
try? await managedObjectContext.performChanges {
|
try? await managedObjectContext.performChanges {
|
||||||
guard let authenticationBox = _authenticationBox else { return }
|
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||||
guard let tag = record.object(in: managedObjectContext) else { return }
|
guard let tag = record.object(in: managedObjectContext) else { return }
|
||||||
|
|
||||||
|
@ -93,13 +92,12 @@ extension DataSourceFacade {
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
|
|
||||||
static func responseToDeleteSearchHistory(
|
static func responseToDeleteSearchHistory(
|
||||||
provider: DataSourceProvider
|
provider: DataSourceProvider & AuthContextProvider
|
||||||
) async throws {
|
) async throws {
|
||||||
let _authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value
|
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
try await managedObjectContext.performChanges {
|
||||||
guard let authenticationBox = _authenticationBox else { return }
|
|
||||||
guard let _ = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
guard let _ = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||||
let request = SearchHistory.sortedFetchRequest
|
let request = SearchHistory.sortedFetchRequest
|
||||||
request.predicate = SearchHistory.predicate(
|
request.predicate = SearchHistory.predicate(
|
||||||
|
|
|
@ -15,13 +15,12 @@ import MastodonLocalization
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
|
|
||||||
static func responseToDeleteStatus(
|
static func responseToDeleteStatus(
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
_ = try await dependency.context.apiService.deleteStatus(
|
_ = try await dependency.context.apiService.deleteStatus(
|
||||||
status: status,
|
status: status,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,10 +80,9 @@ extension DataSourceFacade {
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
@MainActor
|
@MainActor
|
||||||
static func responseToActionToolbar(
|
static func responseToActionToolbar(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
status: ManagedObjectRecord<Status>,
|
status: ManagedObjectRecord<Status>,
|
||||||
action: ActionToolbarContainer.Action,
|
action: ActionToolbarContainer.Action,
|
||||||
authenticationBox: MastodonAuthenticationBox,
|
|
||||||
sender: UIButton
|
sender: UIButton
|
||||||
) async throws {
|
) async throws {
|
||||||
let managedObjectContext = provider.context.managedObjectContext
|
let managedObjectContext = provider.context.managedObjectContext
|
||||||
|
@ -100,16 +98,15 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .reply:
|
case .reply:
|
||||||
guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||||
selectionFeedbackGenerator.selectionChanged()
|
selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
let composeViewModel = ComposeViewModel(
|
let composeViewModel = ComposeViewModel(
|
||||||
context: provider.context,
|
context: provider.context,
|
||||||
composeKind: .reply(status: status),
|
composeKind: .reply(status: status),
|
||||||
authenticationBox: authenticationBox
|
authContext: provider.authContext
|
||||||
)
|
)
|
||||||
provider.coordinator.present(
|
_ = provider.coordinator.present(
|
||||||
scene: .compose(viewModel: composeViewModel),
|
scene: .compose(viewModel: composeViewModel),
|
||||||
from: provider,
|
from: provider,
|
||||||
transition: .modal(animated: true, completion: nil)
|
transition: .modal(animated: true, completion: nil)
|
||||||
|
@ -117,20 +114,17 @@ extension DataSourceFacade {
|
||||||
case .reblog:
|
case .reblog:
|
||||||
try await DataSourceFacade.responseToStatusReblogAction(
|
try await DataSourceFacade.responseToStatusReblogAction(
|
||||||
provider: provider,
|
provider: provider,
|
||||||
status: status,
|
status: status
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
case .like:
|
case .like:
|
||||||
try await DataSourceFacade.responseToStatusFavoriteAction(
|
try await DataSourceFacade.responseToStatusFavoriteAction(
|
||||||
provider: provider,
|
provider: provider,
|
||||||
status: status,
|
status: status
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
case .bookmark:
|
case .bookmark:
|
||||||
try await DataSourceFacade.responseToStatusBookmarkAction(
|
try await DataSourceFacade.responseToStatusBookmarkAction(
|
||||||
provider: provider,
|
provider: provider,
|
||||||
status: status,
|
status: status
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
case .share:
|
case .share:
|
||||||
try await DataSourceFacade.responseToStatusShareAction(
|
try await DataSourceFacade.responseToStatusShareAction(
|
||||||
|
@ -155,10 +149,9 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static func responseToMenuAction(
|
static func responseToMenuAction(
|
||||||
dependency: NeedsDependency & UIViewController,
|
dependency: UIViewController & NeedsDependency & AuthContextProvider,
|
||||||
action: MastodonMenu.Action,
|
action: MastodonMenu.Action,
|
||||||
menuContext: MenuContext,
|
menuContext: MenuContext
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws {
|
) async throws {
|
||||||
switch action {
|
switch action {
|
||||||
case .muteUser(let actionContext):
|
case .muteUser(let actionContext):
|
||||||
|
@ -181,8 +174,7 @@ extension DataSourceFacade {
|
||||||
guard let user = _user else { return }
|
guard let user = _user else { return }
|
||||||
try await DataSourceFacade.responseToUserMuteAction(
|
try await DataSourceFacade.responseToUserMuteAction(
|
||||||
dependency: dependency,
|
dependency: dependency,
|
||||||
user: user,
|
user: user
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
@ -210,8 +202,7 @@ extension DataSourceFacade {
|
||||||
guard let user = _user else { return }
|
guard let user = _user else { return }
|
||||||
try await DataSourceFacade.responseToUserBlockAction(
|
try await DataSourceFacade.responseToUserBlockAction(
|
||||||
dependency: dependency,
|
dependency: dependency,
|
||||||
user: user,
|
user: user
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
@ -225,11 +216,12 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
let reportViewModel = ReportViewModel(
|
let reportViewModel = ReportViewModel(
|
||||||
context: dependency.context,
|
context: dependency.context,
|
||||||
|
authContext: dependency.authContext,
|
||||||
user: user,
|
user: user,
|
||||||
status: menuContext.status
|
status: menuContext.status
|
||||||
)
|
)
|
||||||
|
|
||||||
dependency.coordinator.present(
|
_ = dependency.coordinator.present(
|
||||||
scene: .report(viewModel: reportViewModel),
|
scene: .report(viewModel: reportViewModel),
|
||||||
from: dependency,
|
from: dependency,
|
||||||
transition: .modal(animated: true, completion: nil)
|
transition: .modal(animated: true, completion: nil)
|
||||||
|
@ -246,7 +238,7 @@ extension DataSourceFacade {
|
||||||
user: user
|
user: user
|
||||||
)
|
)
|
||||||
guard let activityViewController = _activityViewController else { return }
|
guard let activityViewController = _activityViewController else { return }
|
||||||
dependency.coordinator.present(
|
_ = dependency.coordinator.present(
|
||||||
scene: .activityViewController(
|
scene: .activityViewController(
|
||||||
activityViewController: activityViewController,
|
activityViewController: activityViewController,
|
||||||
sourceView: menuContext.button,
|
sourceView: menuContext.button,
|
||||||
|
@ -270,8 +262,7 @@ extension DataSourceFacade {
|
||||||
Task {
|
Task {
|
||||||
try await DataSourceFacade.responseToDeleteStatus(
|
try await DataSourceFacade.responseToDeleteStatus(
|
||||||
dependency: dependency,
|
dependency: dependency,
|
||||||
status: status,
|
status: status
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func coordinateToStatusThreadScene(
|
static func coordinateToStatusThreadScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
target: StatusTarget,
|
target: StatusTarget,
|
||||||
status: ManagedObjectRecord<Status>
|
status: ManagedObjectRecord<Status>
|
||||||
) async {
|
) async {
|
||||||
|
@ -39,14 +40,15 @@ extension DataSourceFacade {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static func coordinateToStatusThreadScene(
|
static func coordinateToStatusThreadScene(
|
||||||
provider: DataSourceProvider,
|
provider: DataSourceProvider & AuthContextProvider,
|
||||||
root: StatusItem.Thread
|
root: StatusItem.Thread
|
||||||
) async {
|
) async {
|
||||||
let threadViewModel = ThreadViewModel(
|
let threadViewModel = ThreadViewModel(
|
||||||
context: provider.context,
|
context: provider.context,
|
||||||
|
authContext: provider.authContext,
|
||||||
optionalRoot: root
|
optionalRoot: root
|
||||||
)
|
)
|
||||||
provider.coordinator.present(
|
_ = provider.coordinator.present(
|
||||||
scene: .thread(viewModel: threadViewModel),
|
scene: .thread(viewModel: threadViewModel),
|
||||||
from: provider,
|
from: provider,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
import MastodonUI
|
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
|
import MastodonUI
|
||||||
|
|
||||||
// MARK: - Notification AuthorMenuAction
|
// MARK: - Notification AuthorMenuAction
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
notificationView: NotificationView,
|
notificationView: NotificationView,
|
||||||
menuButton button: UIButton,
|
menuButton button: UIButton,
|
||||||
didSelectAction action: MastodonMenu.Action
|
didSelectAction action: MastodonMenu.Action
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
guard let item = await item(from: source) else {
|
guard let item = await item(from: source) else {
|
||||||
|
@ -47,15 +47,14 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
status: nil,
|
status: nil,
|
||||||
button: button,
|
button: button,
|
||||||
barButtonItem: nil
|
barButtonItem: nil
|
||||||
),
|
)
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Notification Author Avatar
|
// MARK: - Notification Author Avatar
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
notificationView: NotificationView,
|
notificationView: NotificationView,
|
||||||
|
@ -88,7 +87,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Follow Request
|
// MARK: - Follow Request
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
|
@ -106,15 +105,10 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try await DataSourceFacade.responseToUserFollowRequestAction(
|
try await DataSourceFacade.responseToUserFollowRequestAction(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
notification: notification,
|
notification: notification,
|
||||||
query: .accept,
|
query: .accept
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
@ -135,15 +129,10 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try await DataSourceFacade.responseToUserFollowRequestAction(
|
try await DataSourceFacade.responseToUserFollowRequestAction(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
notification: notification,
|
notification: notification,
|
||||||
query: .reject,
|
query: .reject
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
@ -151,7 +140,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Status Content
|
// MARK: - Status Content
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
notificationView: NotificationView,
|
notificationView: NotificationView,
|
||||||
|
@ -279,7 +268,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Status Toolbar
|
// MARK: - Status Toolbar
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
notificationView: NotificationView,
|
notificationView: NotificationView,
|
||||||
|
@ -287,7 +276,6 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
buttonDidPressed button: UIButton,
|
buttonDidPressed button: UIButton,
|
||||||
action: ActionToolbarContainer.Action
|
action: ActionToolbarContainer.Action
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
guard let item = await item(from: source) else {
|
guard let item = await item(from: source) else {
|
||||||
|
@ -311,7 +299,6 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
provider: self,
|
provider: self,
|
||||||
status: status,
|
status: status,
|
||||||
action: action,
|
action: action,
|
||||||
authenticationBox: authenticationBox,
|
|
||||||
sender: button
|
sender: button
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
|
@ -319,7 +306,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Status Author Avatar
|
// MARK: - Status Author Avatar
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
notificationView: NotificationView,
|
notificationView: NotificationView,
|
||||||
|
@ -354,7 +341,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Status Content
|
// MARK: - Status Content
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
|
@ -530,7 +517,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: a11y
|
// MARK: a11y
|
||||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, accessibilityActivate: Void) {
|
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, accessibilityActivate: Void) {
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
|
import MastodonCore
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
|
||||||
// MARK: - header
|
// MARK: - header
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
|
@ -64,7 +65,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - avatar button
|
// MARK: - avatar button
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
|
@ -92,7 +93,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - content
|
// MARK: - content
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
|
@ -169,7 +170,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & MediaPrev
|
||||||
|
|
||||||
|
|
||||||
// MARK: - poll
|
// MARK: - poll
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
|
@ -177,7 +178,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
pollTableView tableView: UITableView,
|
pollTableView tableView: UITableView,
|
||||||
didSelectRowAt indexPath: IndexPath
|
didSelectRowAt indexPath: IndexPath
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
||||||
guard let pollItem = pollTableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }
|
guard let pollItem = pollTableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
_ = try await context.apiService.vote(
|
_ = try await context.apiService.vote(
|
||||||
poll: poll,
|
poll: poll,
|
||||||
choices: [choice],
|
choices: [choice],
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): vote poll for \(choice) success")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): vote poll for \(choice) success")
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -248,7 +248,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
statusView: StatusView,
|
statusView: StatusView,
|
||||||
pollVoteButtonPressed button: UIButton
|
pollVoteButtonPressed button: UIButton
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
||||||
guard let firstPollItem = pollTableViewDiffableDataSource.snapshot().itemIdentifiers.first else { return }
|
guard let firstPollItem = pollTableViewDiffableDataSource.snapshot().itemIdentifiers.first else { return }
|
||||||
guard case let .option(firstPollOption) = firstPollItem else { return }
|
guard case let .option(firstPollOption) = firstPollItem else { return }
|
||||||
|
@ -284,7 +283,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
_ = try await context.apiService.vote(
|
_ = try await context.apiService.vote(
|
||||||
poll: poll,
|
poll: poll,
|
||||||
choices: choices,
|
choices: choices,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): vote poll for \(choices) success")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): vote poll for \(choices) success")
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -303,7 +302,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - toolbar
|
// MARK: - toolbar
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
statusView: StatusView,
|
statusView: StatusView,
|
||||||
|
@ -311,7 +310,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
buttonDidPressed button: UIButton,
|
buttonDidPressed button: UIButton,
|
||||||
action: ActionToolbarContainer.Action
|
action: ActionToolbarContainer.Action
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
guard let item = await item(from: source) else {
|
guard let item = await item(from: source) else {
|
||||||
|
@ -327,7 +325,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
provider: self,
|
provider: self,
|
||||||
status: status,
|
status: status,
|
||||||
action: action,
|
action: action,
|
||||||
authenticationBox: authenticationBox,
|
|
||||||
sender: button
|
sender: button
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
|
@ -336,14 +333,13 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - menu button
|
// MARK: - menu button
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
statusView: StatusView,
|
statusView: StatusView,
|
||||||
menuButton button: UIButton,
|
menuButton button: UIButton,
|
||||||
didSelectAction action: MastodonMenu.Action
|
didSelectAction action: MastodonMenu.Action
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
guard let item = await item(from: source) else {
|
guard let item = await item(from: source) else {
|
||||||
|
@ -372,8 +368,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
status: status,
|
status: status,
|
||||||
button: button,
|
button: button,
|
||||||
barButtonItem: nil
|
barButtonItem: nil
|
||||||
),
|
)
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
@ -475,7 +470,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - StatusMetricView
|
// MARK: - StatusMetricView
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton) {
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton) {
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
|
@ -489,6 +484,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
let userListViewModel = UserListViewModel(
|
let userListViewModel = UserListViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: authContext,
|
||||||
kind: .rebloggedBy(status: status)
|
kind: .rebloggedBy(status: status)
|
||||||
)
|
)
|
||||||
await coordinator.present(
|
await coordinator.present(
|
||||||
|
@ -512,6 +508,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
let userListViewModel = UserListViewModel(
|
let userListViewModel = UserListViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: authContext,
|
||||||
kind: .favoritedBy(status: status)
|
kind: .favoritedBy(status: status)
|
||||||
)
|
)
|
||||||
await coordinator.present(
|
await coordinator.present(
|
||||||
|
@ -524,7 +521,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: a11y
|
// MARK: a11y
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, accessibilityActivate: Void) {
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, accessibilityActivate: Void) {
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & StatusTableViewControllerNavigateableRelay {
|
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & StatusTableViewControllerNavigateableRelay {
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider {
|
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func statusKeyCommandHandler(_ sender: UIKeyCommand) {
|
func statusKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||||
guard let rawValue = sender.propertyList as? String,
|
guard let rawValue = sender.propertyList as? String,
|
||||||
|
@ -53,7 +54,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
||||||
}
|
}
|
||||||
|
|
||||||
// status coordinate
|
// status coordinate
|
||||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider {
|
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func statusRecord() async -> ManagedObjectRecord<Status>? {
|
private func statusRecord() async -> ManagedObjectRecord<Status>? {
|
||||||
|
@ -93,14 +94,13 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
||||||
private func replyStatus() async {
|
private func replyStatus() async {
|
||||||
guard let status = await statusRecord() else { return }
|
guard let status = await statusRecord() else { return }
|
||||||
|
|
||||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||||
selectionFeedbackGenerator.selectionChanged()
|
selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
let composeViewModel = ComposeViewModel(
|
let composeViewModel = ComposeViewModel(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
composeKind: .reply(status: status),
|
composeKind: .reply(status: status),
|
||||||
authenticationBox: authenticationBox
|
authContext: authContext
|
||||||
)
|
)
|
||||||
self.coordinator.present(
|
self.coordinator.present(
|
||||||
scene: .compose(viewModel: composeViewModel),
|
scene: .compose(viewModel: composeViewModel),
|
||||||
|
@ -144,19 +144,16 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle
|
// toggle
|
||||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider {
|
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func toggleReblog() async {
|
private func toggleReblog() async {
|
||||||
guard let status = await statusRecord() else { return }
|
guard let status = await statusRecord() else { return }
|
||||||
|
|
||||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await DataSourceFacade.responseToStatusReblogAction(
|
try await DataSourceFacade.responseToStatusReblogAction(
|
||||||
provider: self,
|
provider: self,
|
||||||
status: status,
|
status: status
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
|
@ -167,13 +164,10 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
||||||
private func toggleFavorite() async {
|
private func toggleFavorite() async {
|
||||||
guard let status = await statusRecord() else { return }
|
guard let status = await statusRecord() else { return }
|
||||||
|
|
||||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await DataSourceFacade.responseToStatusFavoriteAction(
|
try await DataSourceFacade.responseToStatusFavoriteAction(
|
||||||
provider: self,
|
provider: self,
|
||||||
status: status,
|
status: status
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
extension TableViewControllerNavigateableCore where Self: TableViewControllerNavigateableRelay {
|
extension TableViewControllerNavigateableCore where Self: TableViewControllerNavigateableRelay {
|
||||||
var navigationKeyCommands: [UIKeyCommand] {
|
var navigationKeyCommands: [UIKeyCommand] {
|
||||||
|
@ -124,7 +125,7 @@ extension TableViewControllerNavigateableCore {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TableViewControllerNavigateableCore where Self: DataSourceProvider {
|
extension TableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||||
func open() {
|
func open() {
|
||||||
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
let source = DataSourceItem.Source(indexPath: indexPathForSelectedRow)
|
let source = DataSourceItem.Source(indexPath: indexPathForSelectedRow)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import MastodonCore
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
extension UITableViewDelegate where Self: DataSourceProvider {
|
extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||||
|
|
||||||
func aspectTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func aspectTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): indexPath: \(indexPath.debugDescription)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): indexPath: \(indexPath.debugDescription)")
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// Created by Cirno MainasuK on 2021-9-13.
|
// Created by Cirno MainasuK on 2021-9-13.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
|
@ -14,43 +15,43 @@ import MastodonMeta
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
|
||||||
final class AccountListViewModel {
|
final class AccountListViewModel: NSObject {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
|
let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController<MastodonAuthentication>
|
||||||
|
|
||||||
// output
|
// output
|
||||||
let authentications = CurrentValueSubject<[Item], Never>([])
|
@Published var authentications: [ManagedObjectRecord<MastodonAuthentication>] = []
|
||||||
let activeMastodonUserObjectID = CurrentValueSubject<NSManagedObjectID?, Never>(nil)
|
@Published var items: [Item] = []
|
||||||
|
|
||||||
let dataSourceDidUpdate = PassthroughSubject<Void, Never>()
|
let dataSourceDidUpdate = PassthroughSubject<Void, Never>()
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>!
|
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
|
self.mastodonAuthenticationFetchedResultsController = {
|
||||||
|
let fetchRequest = MastodonAuthentication.sortedFetchRequest
|
||||||
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
|
fetchRequest.fetchBatchSize = 20
|
||||||
|
let controller = NSFetchedResultsController(
|
||||||
|
fetchRequest: fetchRequest,
|
||||||
|
managedObjectContext: context.managedObjectContext,
|
||||||
|
sectionNameKeyPath: nil,
|
||||||
|
cacheName: nil
|
||||||
|
)
|
||||||
|
return controller
|
||||||
|
}()
|
||||||
|
super.init()
|
||||||
|
// end init
|
||||||
|
|
||||||
|
mastodonAuthenticationFetchedResultsController.delegate = self
|
||||||
|
|
||||||
Publishers.CombineLatest(
|
$authentications
|
||||||
context.authenticationService.mastodonAuthentications,
|
|
||||||
context.authenticationService.activeMastodonAuthentication
|
|
||||||
)
|
|
||||||
.sink { [weak self] authentications, activeAuthentication in
|
|
||||||
guard let self = self else { return }
|
|
||||||
var items: [Item] = []
|
|
||||||
var activeMastodonUserObjectID: NSManagedObjectID?
|
|
||||||
for authentication in authentications {
|
|
||||||
let item = Item.authentication(objectID: authentication.objectID)
|
|
||||||
items.append(item)
|
|
||||||
if authentication === activeAuthentication {
|
|
||||||
activeMastodonUserObjectID = authentication.user.objectID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.authentications.value = items
|
|
||||||
self.activeMastodonUserObjectID.value = activeMastodonUserObjectID
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
authentications
|
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] authentications in
|
.sink { [weak self] authentications in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -58,7 +59,10 @@ final class AccountListViewModel {
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
snapshot.appendItems(authentications, toSection: .main)
|
let authenticationItems: [Item] = authentications.map {
|
||||||
|
Item.authentication(record: $0)
|
||||||
|
}
|
||||||
|
snapshot.appendItems(authenticationItems, toSection: .main)
|
||||||
snapshot.appendItems([.addAccount], toSection: .main)
|
snapshot.appendItems([.addAccount], toSection: .main)
|
||||||
|
|
||||||
diffableDataSource.apply(snapshot) {
|
diffableDataSource.apply(snapshot) {
|
||||||
|
@ -76,7 +80,7 @@ extension AccountListViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case authentication(objectID: NSManagedObjectID)
|
case authentication(record: ManagedObjectRecord<MastodonAuthentication>)
|
||||||
case addAccount
|
case addAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,14 +90,17 @@ extension AccountListViewModel {
|
||||||
) {
|
) {
|
||||||
diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
|
diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
|
||||||
switch item {
|
switch item {
|
||||||
case .authentication(let objectID):
|
case .authentication(let record):
|
||||||
let authentication = managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
|
||||||
AccountListViewModel.configure(
|
if let authentication = record.object(in: managedObjectContext),
|
||||||
cell: cell,
|
let activeAuthentication = self.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||||
authentication: authentication,
|
{
|
||||||
activeMastodonUserObjectID: self.activeMastodonUserObjectID.eraseToAnyPublisher()
|
AccountListViewModel.configure(
|
||||||
)
|
cell: cell,
|
||||||
|
authentication: authentication,
|
||||||
|
activeAuthentication: activeAuthentication
|
||||||
|
)
|
||||||
|
}
|
||||||
return cell
|
return cell
|
||||||
case .addAccount:
|
case .addAccount:
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AddAccountTableViewCell.self), for: indexPath) as! AddAccountTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AddAccountTableViewCell.self), for: indexPath) as! AddAccountTableViewCell
|
||||||
|
@ -109,7 +116,7 @@ extension AccountListViewModel {
|
||||||
static func configure(
|
static func configure(
|
||||||
cell: AccountListTableViewCell,
|
cell: AccountListTableViewCell,
|
||||||
authentication: MastodonAuthentication,
|
authentication: MastodonAuthentication,
|
||||||
activeMastodonUserObjectID: AnyPublisher<NSManagedObjectID?, Never>
|
activeAuthentication: MastodonAuthentication
|
||||||
) {
|
) {
|
||||||
let user = authentication.user
|
let user = authentication.user
|
||||||
|
|
||||||
|
@ -138,19 +145,14 @@ extension AccountListViewModel {
|
||||||
cell.badgeButton.setBadge(number: count)
|
cell.badgeButton.setBadge(number: count)
|
||||||
|
|
||||||
// checkmark
|
// checkmark
|
||||||
activeMastodonUserObjectID
|
let isActive = activeAuthentication.userID == authentication.userID
|
||||||
.receive(on: DispatchQueue.main)
|
cell.tintColor = .label
|
||||||
.sink { objectID in
|
cell.checkmarkImageView.isHidden = !isActive
|
||||||
let isCurrentUser = user.objectID == objectID
|
if isActive {
|
||||||
cell.tintColor = .label
|
cell.accessibilityTraits.insert(.selected)
|
||||||
cell.checkmarkImageView.isHidden = !isCurrentUser
|
} else {
|
||||||
if isCurrentUser {
|
cell.accessibilityTraits.remove(.selected)
|
||||||
cell.accessibilityTraits.insert(.selected)
|
}
|
||||||
} else {
|
|
||||||
cell.accessibilityTraits.remove(.selected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
cell.accessibilityLabel = [
|
cell.accessibilityLabel = [
|
||||||
cell.nameLabel.text,
|
cell.nameLabel.text,
|
||||||
|
@ -161,3 +163,21 @@ extension AccountListViewModel {
|
||||||
.joined(separator: " ")
|
.joined(separator: " ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - NSFetchedResultsControllerDelegate
|
||||||
|
extension AccountListViewModel: NSFetchedResultsControllerDelegate {
|
||||||
|
|
||||||
|
public func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||||
|
guard controller === mastodonAuthenticationFetchedResultsController else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ final class AccountListViewController: UIViewController, NeedsDependency {
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
private(set) lazy var viewModel = AccountListViewModel(context: context)
|
var viewModel: AccountListViewModel!
|
||||||
|
|
||||||
private(set) lazy var addBarButtonItem: UIBarButtonItem = {
|
private(set) lazy var addBarButtonItem: UIBarButtonItem = {
|
||||||
let barButtonItem = UIBarButtonItem(
|
let barButtonItem = UIBarButtonItem(
|
||||||
|
@ -64,7 +64,10 @@ extension AccountListViewController: PanModalPresentable {
|
||||||
return .contentHeight(CGFloat(height))
|
return .contentHeight(CGFloat(height))
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = viewModel.context.authenticationService.mastodonAuthentications.value.count + 1
|
let request = MastodonAuthentication.sortedFetchRequest
|
||||||
|
let authenticationCount = (try? context.managedObjectContext.count(for: request)) ?? 0
|
||||||
|
|
||||||
|
let count = authenticationCount + 1
|
||||||
let height = calculateHeight(of: count)
|
let height = calculateHeight(of: count)
|
||||||
return .contentHeight(height)
|
return .contentHeight(height)
|
||||||
}
|
}
|
||||||
|
@ -174,16 +177,14 @@ extension AccountListViewController: UITableViewDelegate {
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
|
||||||
switch item {
|
switch item {
|
||||||
case .authentication(let objectID):
|
case .authentication(let record):
|
||||||
assert(Thread.isMainThread)
|
assert(Thread.isMainThread)
|
||||||
let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
guard let authentication = record.object(in: context.managedObjectContext) else { return }
|
||||||
context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
Task { @MainActor in
|
||||||
.receive(on: DispatchQueue.main)
|
let isActive = try await context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
||||||
.sink { [weak self] result in
|
guard isActive else { return }
|
||||||
guard let self = self else { return }
|
self.coordinator.setup()
|
||||||
self.coordinator.setup()
|
} // end Task
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
case .addAccount:
|
case .addAccount:
|
||||||
// TODO: add dismiss entry for welcome scene
|
// TODO: add dismiss entry for welcome scene
|
||||||
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
|
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
|
||||||
|
|
|
@ -133,11 +133,6 @@ extension AutoCompleteViewModel.State {
|
||||||
await enter(state: Fail.self)
|
await enter(state: Fail.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
await enter(state: Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let searchText = viewModel.inputText.value
|
let searchText = viewModel.inputText.value
|
||||||
let searchType = AutoCompleteViewModel.SearchType(inputText: searchText) ?? .default
|
let searchType = AutoCompleteViewModel.SearchType(inputText: searchText) ?? .default
|
||||||
|
@ -154,7 +149,7 @@ extension AutoCompleteViewModel.State {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.search(
|
let response = try await viewModel.context.apiService.search(
|
||||||
query: query,
|
query: query,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
await enter(state: Idle.self)
|
await enter(state: Idle.self)
|
||||||
|
|
|
@ -17,6 +17,7 @@ final class AutoCompleteViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
public let inputText = CurrentValueSubject<String, Never>("") // contains "@" or "#" prefix
|
public let inputText = CurrentValueSubject<String, Never>("") // contains "@" or "#" prefix
|
||||||
public let symbolBoundingRect = CurrentValueSubject<CGRect, Never>(.zero)
|
public let symbolBoundingRect = CurrentValueSubject<CGRect, Never>(.zero)
|
||||||
public let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
public let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
||||||
|
@ -36,8 +37,9 @@ final class AutoCompleteViewModel {
|
||||||
return stateMachine
|
return stateMachine
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
|
|
||||||
autoCompleteItems
|
autoCompleteItems
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
|
|
@ -137,7 +137,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
|
private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
|
||||||
let viewController = AutoCompleteViewController()
|
let viewController = AutoCompleteViewController()
|
||||||
viewController.viewModel = AutoCompleteViewModel(context: context)
|
viewController.viewModel = AutoCompleteViewModel(context: context, authContext: viewModel.authContext)
|
||||||
viewController.delegate = self
|
viewController.delegate = self
|
||||||
viewController.viewModel.customEmojiViewModel.value = viewModel.customEmojiViewModel
|
viewController.viewModel.customEmojiViewModel.value = viewModel.customEmojiViewModel
|
||||||
return viewController
|
return viewController
|
||||||
|
|
|
@ -29,8 +29,11 @@ final class ComposeViewModel: NSObject {
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let composeKind: ComposeStatusSection.ComposeKind
|
let composeKind: ComposeStatusSection.ComposeKind
|
||||||
let authenticationBox: MastodonAuthenticationBox
|
let authContext: AuthContext
|
||||||
|
|
||||||
|
var authenticationBox: MastodonAuthenticationBox {
|
||||||
|
authContext.mastodonAuthenticationBox
|
||||||
|
}
|
||||||
|
|
||||||
@Published var isPollComposing = false
|
@Published var isPollComposing = false
|
||||||
@Published var isCustomEmojiComposing = false
|
@Published var isCustomEmojiComposing = false
|
||||||
|
@ -116,11 +119,12 @@ final class ComposeViewModel: NSObject {
|
||||||
init(
|
init(
|
||||||
context: AppContext,
|
context: AppContext,
|
||||||
composeKind: ComposeStatusSection.ComposeKind,
|
composeKind: ComposeStatusSection.ComposeKind,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authContext: AuthContext
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.composeKind = composeKind
|
self.composeKind = composeKind
|
||||||
self.authenticationBox = authenticationBox
|
self.authContext = authContext
|
||||||
|
|
||||||
self.title = {
|
self.title = {
|
||||||
switch composeKind {
|
switch composeKind {
|
||||||
case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
|
case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
|
||||||
|
@ -130,8 +134,7 @@ final class ComposeViewModel: NSObject {
|
||||||
self.selectedStatusVisibility = {
|
self.selectedStatusVisibility = {
|
||||||
// default private when user locked
|
// default private when user locked
|
||||||
var visibility: ComposeToolbarView.VisibilitySelectionType = {
|
var visibility: ComposeToolbarView.VisibilitySelectionType = {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value,
|
guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||||
let author = authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
|
||||||
else {
|
else {
|
||||||
return .public
|
return .public
|
||||||
}
|
}
|
||||||
|
@ -168,15 +171,12 @@ final class ComposeViewModel: NSObject {
|
||||||
self.instanceConfiguration = {
|
self.instanceConfiguration = {
|
||||||
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
||||||
context.managedObjectContext.performAndWait {
|
context.managedObjectContext.performAndWait {
|
||||||
guard let authentication = authenticationBox.authenticationRecord.object(in: context.managedObjectContext)
|
guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) else { return }
|
||||||
else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configuration = authentication.instance?.configuration
|
configuration = authentication.instance?.configuration
|
||||||
}
|
}
|
||||||
return configuration
|
return configuration
|
||||||
}()
|
}()
|
||||||
self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authenticationBox.domain)
|
self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authContext.mastodonAuthenticationBox.domain)
|
||||||
super.init()
|
super.init()
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,11 @@ extension DiscoveryCommunityViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension DiscoveryCommunityViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension DiscoveryCommunityViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension DiscoveryCommunityViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:CommunityViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:CommunityViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -18,6 +18,7 @@ extension DiscoveryCommunityViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||||
filterContext: .none,
|
filterContext: .none,
|
||||||
|
|
|
@ -136,11 +136,6 @@ extension DiscoveryCommunityViewModel.State {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let maxID = self.maxID
|
let maxID = self.maxID
|
||||||
let isReloading = maxID == nil
|
let isReloading = maxID == nil
|
||||||
|
|
||||||
|
@ -156,7 +151,7 @@ extension DiscoveryCommunityViewModel.State {
|
||||||
minID: nil,
|
minID: nil,
|
||||||
limit: 20
|
limit: 20
|
||||||
),
|
),
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
let newMaxID = response.link?.maxID
|
let newMaxID = response.link?.maxID
|
||||||
|
|
|
@ -22,6 +22,7 @@ final class DiscoveryCommunityViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
||||||
let statusFetchedResultsController: StatusFetchedResultsController
|
let statusFetchedResultsController: StatusFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
@ -43,20 +44,15 @@ final class DiscoveryCommunityViewModel {
|
||||||
|
|
||||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalTweetPredicate: nil
|
additionalTweetPredicate: nil
|
||||||
)
|
)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthentication
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
|
@ -25,11 +25,8 @@ public class DiscoveryViewController: TabmanViewController, NeedsDependency {
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
private(set) lazy var viewModel = DiscoveryViewModel(
|
var viewModel: DiscoveryViewModel!
|
||||||
context: context,
|
|
||||||
coordinator: coordinator
|
|
||||||
)
|
|
||||||
|
|
||||||
private(set) lazy var buttonBar: TMBar.ButtonBar = {
|
private(set) lazy var buttonBar: TMBar.ButtonBar = {
|
||||||
let buttonBar = TMBar.ButtonBar()
|
let buttonBar = TMBar.ButtonBar()
|
||||||
|
|
|
@ -18,6 +18,7 @@ final class DiscoveryViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let discoveryPostsViewController: DiscoveryPostsViewController
|
let discoveryPostsViewController: DiscoveryPostsViewController
|
||||||
let discoveryHashtagsViewController: DiscoveryHashtagsViewController
|
let discoveryHashtagsViewController: DiscoveryHashtagsViewController
|
||||||
let discoveryNewsViewController: DiscoveryNewsViewController
|
let discoveryNewsViewController: DiscoveryNewsViewController
|
||||||
|
@ -26,41 +27,43 @@ final class DiscoveryViewModel {
|
||||||
|
|
||||||
@Published var viewControllers: [ScrollViewContainer & PageViewController]
|
@Published var viewControllers: [ScrollViewContainer & PageViewController]
|
||||||
|
|
||||||
init(context: AppContext, coordinator: SceneCoordinator) {
|
init(context: AppContext, coordinator: SceneCoordinator, authContext: AuthContext) {
|
||||||
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
|
|
||||||
func setupDependency(_ needsDependency: NeedsDependency) {
|
func setupDependency(_ needsDependency: NeedsDependency) {
|
||||||
needsDependency.context = context
|
needsDependency.context = context
|
||||||
needsDependency.coordinator = coordinator
|
needsDependency.coordinator = coordinator
|
||||||
}
|
}
|
||||||
|
|
||||||
self.context = context
|
|
||||||
discoveryPostsViewController = {
|
discoveryPostsViewController = {
|
||||||
let viewController = DiscoveryPostsViewController()
|
let viewController = DiscoveryPostsViewController()
|
||||||
setupDependency(viewController)
|
setupDependency(viewController)
|
||||||
viewController.viewModel = DiscoveryPostsViewModel(context: context)
|
viewController.viewModel = DiscoveryPostsViewModel(context: context, authContext: authContext)
|
||||||
return viewController
|
return viewController
|
||||||
}()
|
}()
|
||||||
discoveryHashtagsViewController = {
|
discoveryHashtagsViewController = {
|
||||||
let viewController = DiscoveryHashtagsViewController()
|
let viewController = DiscoveryHashtagsViewController()
|
||||||
setupDependency(viewController)
|
setupDependency(viewController)
|
||||||
viewController.viewModel = DiscoveryHashtagsViewModel(context: context)
|
viewController.viewModel = DiscoveryHashtagsViewModel(context: context, authContext: authContext)
|
||||||
return viewController
|
return viewController
|
||||||
}()
|
}()
|
||||||
discoveryNewsViewController = {
|
discoveryNewsViewController = {
|
||||||
let viewController = DiscoveryNewsViewController()
|
let viewController = DiscoveryNewsViewController()
|
||||||
setupDependency(viewController)
|
setupDependency(viewController)
|
||||||
viewController.viewModel = DiscoveryNewsViewModel(context: context)
|
viewController.viewModel = DiscoveryNewsViewModel(context: context, authContext: authContext)
|
||||||
return viewController
|
return viewController
|
||||||
}()
|
}()
|
||||||
discoveryCommunityViewController = {
|
discoveryCommunityViewController = {
|
||||||
let viewController = DiscoveryCommunityViewController()
|
let viewController = DiscoveryCommunityViewController()
|
||||||
setupDependency(viewController)
|
setupDependency(viewController)
|
||||||
viewController.viewModel = DiscoveryCommunityViewModel(context: context)
|
viewController.viewModel = DiscoveryCommunityViewModel(context: context, authContext: authContext)
|
||||||
return viewController
|
return viewController
|
||||||
}()
|
}()
|
||||||
discoveryForYouViewController = {
|
discoveryForYouViewController = {
|
||||||
let viewController = DiscoveryForYouViewController()
|
let viewController = DiscoveryForYouViewController()
|
||||||
setupDependency(viewController)
|
setupDependency(viewController)
|
||||||
viewController.viewModel = DiscoveryForYouViewModel(context: context)
|
viewController.viewModel = DiscoveryForYouViewModel(context: context, authContext: authContext)
|
||||||
return viewController
|
return viewController
|
||||||
}()
|
}()
|
||||||
self.viewControllers = [
|
self.viewControllers = [
|
||||||
|
|
|
@ -101,6 +101,11 @@ extension DiscoveryForYouViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension DiscoveryForYouViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension DiscoveryForYouViewController: UITableViewDelegate {
|
extension DiscoveryForYouViewController: UITableViewDelegate {
|
||||||
|
|
||||||
|
@ -110,9 +115,10 @@ extension DiscoveryForYouViewController: UITableViewDelegate {
|
||||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||||
let profileViewModel = CachedProfileViewModel(
|
let profileViewModel = CachedProfileViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: viewModel.authContext,
|
||||||
mastodonUser: user
|
mastodonUser: user
|
||||||
)
|
)
|
||||||
coordinator.present(
|
_ = coordinator.present(
|
||||||
scene: .profile(viewModel: profileViewModel),
|
scene: .profile(viewModel: profileViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
@ -128,15 +134,13 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
|
||||||
profileCardView: ProfileCardView,
|
profileCardView: ProfileCardView,
|
||||||
relationshipButtonDidPressed button: ProfileRelationshipActionButton
|
relationshipButtonDidPressed button: ProfileRelationshipActionButton
|
||||||
) {
|
) {
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||||
guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
try await DataSourceFacade.responseToUserFollowAction(
|
try await DataSourceFacade.responseToUserFollowAction(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
user: record,
|
user: record
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
@ -157,9 +161,9 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let familiarFollowersViewModel = FamiliarFollowersViewModel(context: context)
|
let familiarFollowersViewModel = FamiliarFollowersViewModel(context: context, authContext: authContext)
|
||||||
familiarFollowersViewModel.familiarFollowers = familiarFollowers
|
familiarFollowersViewModel.familiarFollowers = familiarFollowers
|
||||||
coordinator.present(
|
_ = coordinator.present(
|
||||||
scene: .familiarFollowers(viewModel: familiarFollowersViewModel),
|
scene: .familiarFollowers(viewModel: familiarFollowersViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
|
|
@ -19,6 +19,7 @@ extension DiscoveryForYouViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: DiscoverySection.Configuration(
|
configuration: DiscoverySection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
profileCardTableViewCellDelegate: profileCardTableViewCellDelegate,
|
profileCardTableViewCellDelegate: profileCardTableViewCellDelegate,
|
||||||
familiarFollowers: $familiarFollowers
|
familiarFollowers: $familiarFollowers
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,7 @@ final class DiscoveryForYouViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let userFetchedResultsController: UserFetchedResultsController
|
let userFetchedResultsController: UserFetchedResultsController
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
@ -30,19 +31,15 @@ final class DiscoveryForYouViewModel {
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
||||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.userFetchedResultsController = UserFetchedResultsController(
|
self.userFetchedResultsController = UserFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalPredicate: nil
|
additionalPredicate: nil
|
||||||
)
|
)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: userFetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -59,16 +56,12 @@ extension DiscoveryForYouViewModel {
|
||||||
isFetching = true
|
isFetching = true
|
||||||
defer { isFetching = false }
|
defer { isFetching = false }
|
||||||
|
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
throw APIService.APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let userIDs = try await fetchSuggestionAccounts()
|
let userIDs = try await fetchSuggestionAccounts()
|
||||||
|
|
||||||
let _familiarFollowersResponse = try? await context.apiService.familiarFollowers(
|
let _familiarFollowersResponse = try? await context.apiService.familiarFollowers(
|
||||||
query: .init(ids: userIDs),
|
query: .init(ids: userIDs),
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
familiarFollowers = _familiarFollowersResponse?.value ?? []
|
familiarFollowers = _familiarFollowersResponse?.value ?? []
|
||||||
userFetchedResultsController.userIDs = userIDs
|
userFetchedResultsController.userIDs = userIDs
|
||||||
|
@ -78,14 +71,10 @@ extension DiscoveryForYouViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account.ID] {
|
private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account.ID] {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
throw APIService.APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let response = try await context.apiService.suggestionAccountV2(
|
let response = try await context.apiService.suggestionAccountV2(
|
||||||
query: nil,
|
query: nil,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
let userIDs = response.value.map { $0.account.id }
|
let userIDs = response.value.map { $0.account.id }
|
||||||
return userIDs
|
return userIDs
|
||||||
|
@ -93,7 +82,7 @@ extension DiscoveryForYouViewModel {
|
||||||
// fallback V1
|
// fallback V1
|
||||||
let response = try await context.apiService.suggestionAccount(
|
let response = try await context.apiService.suggestionAccount(
|
||||||
query: nil,
|
query: nil,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
let userIDs = response.value.map { $0.id }
|
let userIDs = response.value.map { $0.id }
|
||||||
return userIDs
|
return userIDs
|
||||||
|
|
|
@ -107,7 +107,7 @@ extension DiscoveryHashtagsViewController: UITableViewDelegate {
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)")
|
||||||
guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name)
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name)
|
||||||
coordinator.present(
|
coordinator.present(
|
||||||
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
|
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
|
@ -217,7 +217,7 @@ extension DiscoveryHashtagsViewController: TableViewControllerNavigateable {
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return }
|
||||||
|
|
||||||
guard case let .hashtag(tag) = item else { return }
|
guard case let .hashtag(tag) = item else { return }
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name)
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name)
|
||||||
coordinator.present(
|
coordinator.present(
|
||||||
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
|
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
|
|
|
@ -15,7 +15,7 @@ extension DiscoveryHashtagsViewModel {
|
||||||
diffableDataSource = DiscoverySection.diffableDataSource(
|
diffableDataSource = DiscoverySection.diffableDataSource(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: DiscoverySection.Configuration()
|
configuration: DiscoverySection.Configuration(authContext: authContext)
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<DiscoverySection, DiscoveryItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<DiscoverySection, DiscoveryItem>()
|
||||||
|
|
|
@ -22,41 +22,37 @@ final class DiscoveryHashtagsViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
||||||
@Published var hashtags: [Mastodon.Entity.Tag] = []
|
@Published var hashtags: [Mastodon.Entity.Tag] = []
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
Publishers.CombineLatest(
|
viewDidAppeared
|
||||||
context.authenticationService.activeMastodonAuthenticationBox,
|
.throttle(for: 3, scheduler: DispatchQueue.main, latest: true)
|
||||||
viewDidAppeared
|
.asyncMap { authenticationBox in
|
||||||
)
|
try await context.apiService.trendHashtags(domain: authContext.mastodonAuthenticationBox.domain, query: nil)
|
||||||
.compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in
|
|
||||||
return authenticationBox
|
|
||||||
}
|
|
||||||
.throttle(for: 3, scheduler: DispatchQueue.main, latest: true)
|
|
||||||
.asyncMap { authenticationBox in
|
|
||||||
try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil)
|
|
||||||
}
|
|
||||||
.retry(3)
|
|
||||||
.map { response in Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { response } }
|
|
||||||
.catch { error in Just(Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { throw error }) }
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] result in
|
|
||||||
guard let self = self else { return }
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
self.hashtags = response.value.filter { !$0.name.isEmpty }
|
|
||||||
case .failure:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
.retry(3)
|
||||||
.store(in: &disposeBag)
|
.map { response in Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { response } }
|
||||||
|
.catch { error in Just(Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { throw error }) }
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] result in
|
||||||
|
guard let self = self else { return }
|
||||||
|
switch result {
|
||||||
|
case .success(let response):
|
||||||
|
self.hashtags = response.value.filter { !$0.name.isEmpty }
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -69,8 +65,7 @@ extension DiscoveryHashtagsViewModel {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func fetch() async throws {
|
func fetch() async throws {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
let response = try await context.apiService.trendHashtags(domain: authContext.mastodonAuthenticationBox.domain, query: nil)
|
||||||
let response = try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil)
|
|
||||||
hashtags = response.value.filter { !$0.name.isEmpty }
|
hashtags = response.value.filter { !$0.name.isEmpty }
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch tags: \(response.value.count)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch tags: \(response.value.count)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ extension DiscoveryNewsViewModel {
|
||||||
diffableDataSource = DiscoverySection.diffableDataSource(
|
diffableDataSource = DiscoverySection.diffableDataSource(
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: DiscoverySection.Configuration()
|
configuration: DiscoverySection.Configuration(authContext: authContext)
|
||||||
)
|
)
|
||||||
|
|
||||||
stateMachine.enter(State.Reloading.self)
|
stateMachine.enter(State.Reloading.self)
|
||||||
|
|
|
@ -136,11 +136,6 @@ extension DiscoveryNewsViewModel.State {
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = self.offset
|
let offset = self.offset
|
||||||
let isReloading = offset == nil
|
let isReloading = offset == nil
|
||||||
|
@ -148,7 +143,7 @@ extension DiscoveryNewsViewModel.State {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.trendLinks(
|
let response = try await viewModel.context.apiService.trendLinks(
|
||||||
domain: authenticationBox.domain,
|
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
|
||||||
query: Mastodon.API.Trends.StatusQuery(
|
query: Mastodon.API.Trends.StatusQuery(
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: nil
|
limit: nil
|
||||||
|
|
|
@ -20,6 +20,7 @@ final class DiscoveryNewsViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
|
||||||
// output
|
// output
|
||||||
|
@ -41,8 +42,9 @@ final class DiscoveryNewsViewModel {
|
||||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||||
@Published var isServerSupportEndpoint = true
|
@Published var isServerSupportEndpoint = true
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
@ -59,11 +61,9 @@ final class DiscoveryNewsViewModel {
|
||||||
|
|
||||||
extension DiscoveryNewsViewModel {
|
extension DiscoveryNewsViewModel {
|
||||||
func checkServerEndpoint() async {
|
func checkServerEndpoint() async {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try await context.apiService.trendLinks(
|
_ = try await context.apiService.trendLinks(
|
||||||
domain: authenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
query: .init(offset: nil, limit: nil)
|
query: .init(offset: nil, limit: nil)
|
||||||
)
|
)
|
||||||
} catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 {
|
} catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 {
|
||||||
|
|
|
@ -128,6 +128,11 @@ extension DiscoveryPostsViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension DiscoveryPostsViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension DiscoveryPostsViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension DiscoveryPostsViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:DiscoveryPostsViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:DiscoveryPostsViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -18,6 +18,7 @@ extension DiscoveryPostsViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||||
filterContext: .none,
|
filterContext: .none,
|
||||||
|
|
|
@ -136,11 +136,6 @@ extension DiscoveryPostsViewModel.State {
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = self.offset
|
let offset = self.offset
|
||||||
let isReloading = offset == nil
|
let isReloading = offset == nil
|
||||||
|
@ -148,7 +143,7 @@ extension DiscoveryPostsViewModel.State {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.trendStatuses(
|
let response = try await viewModel.context.apiService.trendStatuses(
|
||||||
domain: authenticationBox.domain,
|
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
|
||||||
query: Mastodon.API.Trends.StatusQuery(
|
query: Mastodon.API.Trends.StatusQuery(
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: nil
|
limit: nil
|
||||||
|
|
|
@ -20,6 +20,7 @@ final class DiscoveryPostsViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let statusFetchedResultsController: StatusFetchedResultsController
|
let statusFetchedResultsController: StatusFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
|
||||||
|
@ -41,20 +42,16 @@ final class DiscoveryPostsViewModel {
|
||||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||||
@Published var isServerSupportEndpoint = true
|
@Published var isServerSupportEndpoint = true
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalTweetPredicate: nil
|
additionalTweetPredicate: nil
|
||||||
)
|
)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthentication
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await checkServerEndpoint()
|
await checkServerEndpoint()
|
||||||
} // end Task
|
} // end Task
|
||||||
|
@ -68,11 +65,9 @@ final class DiscoveryPostsViewModel {
|
||||||
|
|
||||||
extension DiscoveryPostsViewModel {
|
extension DiscoveryPostsViewModel {
|
||||||
func checkServerEndpoint() async {
|
func checkServerEndpoint() async {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try await context.apiService.trendStatuses(
|
_ = try await context.apiService.trendStatuses(
|
||||||
domain: authenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
query: .init(offset: nil, limit: nil)
|
query: .init(offset: nil, limit: nil)
|
||||||
)
|
)
|
||||||
} catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 {
|
} catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 {
|
||||||
|
|
|
@ -166,17 +166,21 @@ extension HashtagTimelineViewController {
|
||||||
|
|
||||||
@objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
@objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
let composeViewModel = ComposeViewModel(
|
let composeViewModel = ComposeViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
composeKind: .hashtag(hashtag: viewModel.hashtag),
|
composeKind: .hashtag(hashtag: viewModel.hashtag),
|
||||||
authenticationBox: authenticationBox
|
authContext: viewModel.authContext
|
||||||
)
|
)
|
||||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension HashtagTimelineViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension HashtagTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension HashtagTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:HashtagTimelineViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:HashtagTimelineViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -20,6 +20,7 @@ extension HashtagTimelineViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||||
filterContext: .none,
|
filterContext: .none,
|
||||||
|
|
|
@ -135,12 +135,6 @@ extension HashtagTimelineViewModel.State {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
assertionFailure()
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: only set large count when using Wi-Fi
|
// TODO: only set large count when using Wi-Fi
|
||||||
let maxID = self.maxID
|
let maxID = self.maxID
|
||||||
let isReloading = maxID == nil
|
let isReloading = maxID == nil
|
||||||
|
@ -148,10 +142,10 @@ extension HashtagTimelineViewModel.State {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.hashtagTimeline(
|
let response = try await viewModel.context.apiService.hashtagTimeline(
|
||||||
domain: authenticationBox.domain,
|
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
hashtag: viewModel.hashtag,
|
hashtag: viewModel.hashtag,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
let newMaxID: String? = {
|
let newMaxID: String? = {
|
||||||
|
|
|
@ -26,6 +26,7 @@ final class HashtagTimelineViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let fetchedResultsController: StatusFetchedResultsController
|
let fetchedResultsController: StatusFetchedResultsController
|
||||||
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
|
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
|
||||||
let timelinePredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
|
let timelinePredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
|
||||||
|
@ -52,20 +53,16 @@ final class HashtagTimelineViewModel {
|
||||||
}()
|
}()
|
||||||
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<State?, Never>(nil)
|
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<State?, Never>(nil)
|
||||||
|
|
||||||
init(context: AppContext, hashtag: String) {
|
init(context: AppContext, authContext: AuthContext, hashtag: String) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.hashtag = hashtag
|
self.hashtag = hashtag
|
||||||
self.fetchedResultsController = StatusFetchedResultsController(
|
self.fetchedResultsController = StatusFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalTweetPredicate: nil
|
additionalTweetPredicate: nil
|
||||||
)
|
)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: fetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
|
@ -81,8 +81,11 @@ extension HomeTimelineViewController {
|
||||||
},
|
},
|
||||||
UIAction(title: "Account Recommend", image: UIImage(systemName: "human"), attributes: []) { [weak self] action in
|
UIAction(title: "Account Recommend", image: UIImage(systemName: "human"), attributes: []) { [weak self] action in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: self.context)
|
let suggestionAccountViewModel = SuggestionAccountViewModel(
|
||||||
self.coordinator.present(
|
context: self.context,
|
||||||
|
authContext: self.viewModel.authContext
|
||||||
|
)
|
||||||
|
_ = self.coordinator.present(
|
||||||
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .modal(animated: true, completion: nil)
|
transition: .modal(animated: true, completion: nil)
|
||||||
|
@ -150,7 +153,7 @@ extension HomeTimelineViewController {
|
||||||
children: [
|
children: [
|
||||||
UIAction(title: "Badge +1", image: UIImage(systemName: "app.badge.fill"), attributes: []) { [weak self] action in
|
UIAction(title: "Badge +1", image: UIImage(systemName: "app.badge.fill"), attributes: []) { [weak self] action in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let accessToken = self.context.authenticationService.activeMastodonAuthentication.value?.userAccessToken else { return }
|
let accessToken = self.viewModel.authContext.mastodonAuthenticationBox.userAuthorization.accessToken
|
||||||
UserDefaults.shared.increaseNotificationCount(accessToken: accessToken)
|
UserDefaults.shared.increaseNotificationCount(accessToken: accessToken)
|
||||||
self.context.notificationService.applicationIconBadgeNeedsUpdate.send()
|
self.context.notificationService.applicationIconBadgeNeedsUpdate.send()
|
||||||
},
|
},
|
||||||
|
@ -333,7 +336,8 @@ extension HomeTimelineViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func showAccountList(_ sender: UIAction) {
|
@objc private func showAccountList(_ sender: UIAction) {
|
||||||
coordinator.present(scene: .accountList, from: self, transition: .modal(animated: true, completion: nil))
|
let accountListViewModel = AccountListViewModel(context: context, authContext: viewModel.authContext)
|
||||||
|
coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func showProfileAction(_ sender: UIAction) {
|
@objc private func showProfileAction(_ sender: UIAction) {
|
||||||
|
@ -342,7 +346,7 @@ extension HomeTimelineViewController {
|
||||||
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
|
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let textField = alertController?.textFields?.first else { return }
|
guard let textField = alertController?.textFields?.first else { return }
|
||||||
let profileViewModel = RemoteProfileViewModel(context: self.context, userID: textField.text ?? "")
|
let profileViewModel = RemoteProfileViewModel(context: self.context, authContext: self.viewModel.authContext, userID: textField.text ?? "")
|
||||||
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
|
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
alertController.addAction(showAction)
|
alertController.addAction(showAction)
|
||||||
|
@ -357,7 +361,7 @@ extension HomeTimelineViewController {
|
||||||
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
|
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let textField = alertController?.textFields?.first else { return }
|
guard let textField = alertController?.textFields?.first else { return }
|
||||||
let threadViewModel = RemoteThreadViewModel(context: self.context, statusID: textField.text ?? "")
|
let threadViewModel = RemoteThreadViewModel(context: self.context, authContext: self.viewModel.authContext, statusID: textField.text ?? "")
|
||||||
self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show)
|
self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
alertController.addAction(showAction)
|
alertController.addAction(showAction)
|
||||||
|
@ -367,8 +371,6 @@ extension HomeTimelineViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showNotification(_ sender: UIAction, notificationType: Mastodon.Entity.Notification.NotificationType) {
|
private func showNotification(_ sender: UIAction, notificationType: Mastodon.Entity.Notification.NotificationType) {
|
||||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
let alertController = UIAlertController(title: "Enter notification ID", message: nil, preferredStyle: .alert)
|
let alertController = UIAlertController(title: "Enter notification ID", message: nil, preferredStyle: .alert)
|
||||||
alertController.addTextField()
|
alertController.addTextField()
|
||||||
|
|
||||||
|
@ -380,7 +382,7 @@ extension HomeTimelineViewController {
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
let pushNotification = MastodonPushNotification(
|
let pushNotification = MastodonPushNotification(
|
||||||
accessToken: authenticationBox.userAuthorization.accessToken,
|
accessToken: self.viewModel.authContext.mastodonAuthenticationBox.userAuthorization.accessToken,
|
||||||
notificationID: notificationID,
|
notificationID: notificationID,
|
||||||
notificationType: notificationType.rawValue,
|
notificationType: notificationType.rawValue,
|
||||||
preferredLocale: nil,
|
preferredLocale: nil,
|
||||||
|
@ -393,7 +395,7 @@ extension HomeTimelineViewController {
|
||||||
alertController.addAction(showAction)
|
alertController.addAction(showAction)
|
||||||
|
|
||||||
// for multiple accounts debug
|
// for multiple accounts debug
|
||||||
let boxes = self.context.authenticationService.mastodonAuthenticationBoxes.value // already sorted
|
let boxes = self.context.authenticationService.mastodonAuthenticationBoxes // already sorted
|
||||||
if boxes.count >= 2 {
|
if boxes.count >= 2 {
|
||||||
let accessToken = boxes[1].userAuthorization.accessToken
|
let accessToken = boxes[1].userAuthorization.accessToken
|
||||||
let showForSecondaryAction = UIAlertAction(title: "Show for Secondary", style: .default) { [weak self, weak alertController] _ in
|
let showForSecondaryAction = UIAlertAction(title: "Show for Secondary", style: .default) { [weak self, weak alertController] _ in
|
||||||
|
@ -420,12 +422,20 @@ extension HomeTimelineViewController {
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
alertController.addAction(cancelAction)
|
alertController.addAction(cancelAction)
|
||||||
|
|
||||||
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
|
_ = self.coordinator.present(
|
||||||
|
scene: .alertController(alertController: alertController),
|
||||||
|
from: self,
|
||||||
|
transition: .alertController(animated: true, completion: nil)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func showSettings(_ sender: UIAction) {
|
@objc private func showSettings(_ sender: UIAction) {
|
||||||
guard let currentSetting = context.settingService.currentSetting.value else { return }
|
guard let currentSetting = context.settingService.currentSetting.value else { return }
|
||||||
let settingsViewModel = SettingsViewModel(context: context, setting: currentSetting)
|
let settingsViewModel = SettingsViewModel(
|
||||||
|
context: context,
|
||||||
|
authContext: viewModel.authContext,
|
||||||
|
setting: currentSetting
|
||||||
|
)
|
||||||
coordinator.present(
|
coordinator.present(
|
||||||
scene: .settings(viewModel: settingsViewModel),
|
scene: .settings(viewModel: settingsViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
|
|
|
@ -28,7 +28,7 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
private(set) lazy var viewModel = HomeTimelineViewModel(context: context)
|
var viewModel: HomeTimelineViewModel!
|
||||||
|
|
||||||
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
||||||
|
|
||||||
|
@ -373,9 +373,9 @@ extension HomeTimelineViewController {
|
||||||
extension HomeTimelineViewController {
|
extension HomeTimelineViewController {
|
||||||
|
|
||||||
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
|
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
|
||||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context)
|
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context, authContext: viewModel.authContext)
|
||||||
suggestionAccountViewModel.delegate = viewModel
|
suggestionAccountViewModel.delegate = viewModel
|
||||||
coordinator.present(
|
_ = coordinator.present(
|
||||||
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .modal(animated: true, completion: nil)
|
transition: .modal(animated: true, completion: nil)
|
||||||
|
@ -391,7 +391,7 @@ extension HomeTimelineViewController {
|
||||||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard let setting = context.settingService.currentSetting.value else { return }
|
guard let setting = context.settingService.currentSetting.value else { return }
|
||||||
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
|
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
|
||||||
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,12 +403,8 @@ extension HomeTimelineViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func signOutAction(_ sender: UIAction) {
|
@objc func signOutAction(_ sender: UIAction) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox)
|
try await context.authenticationService.signOutMastodonUser(authenticationBox: viewModel.authContext.mastodonAuthenticationBox)
|
||||||
self.coordinator.setup()
|
self.coordinator.setup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,6 +488,11 @@ extension HomeTimelineViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension HomeTimelineViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:HomeTimelineViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:HomeTimelineViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -21,6 +21,7 @@ extension HomeTimelineViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
|
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
|
||||||
filterContext: .home,
|
filterContext: .home,
|
||||||
|
|
|
@ -63,11 +63,6 @@ extension HomeTimelineViewModel.LoadLatestState {
|
||||||
override func didEnter(from previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
// sign out when loading will enter here
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount)
|
let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount)
|
||||||
let parentManagedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext
|
let parentManagedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext
|
||||||
|
@ -85,7 +80,7 @@ extension HomeTimelineViewModel.LoadLatestState {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.homeTimeline(
|
let response = try await viewModel.context.apiService.homeTimeline(
|
||||||
authenticationBox: activeMastodonAuthenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
await enter(state: Idle.self)
|
await enter(state: Idle.self)
|
||||||
|
|
|
@ -64,11 +64,6 @@ extension HomeTimelineViewModel.LoadOldestState {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
assertionFailure()
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let lastFeedRecord = viewModel.fetchedResultsController.records.last else {
|
guard let lastFeedRecord = viewModel.fetchedResultsController.records.last else {
|
||||||
stateMachine.enter(Idle.self)
|
stateMachine.enter(Idle.self)
|
||||||
|
@ -92,7 +87,7 @@ extension HomeTimelineViewModel.LoadOldestState {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.homeTimeline(
|
let response = try await viewModel.context.apiService.homeTimeline(
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
authenticationBox: activeMastodonAuthenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
let statuses = response.value
|
let statuses = response.value
|
||||||
|
|
|
@ -26,6 +26,7 @@ final class HomeTimelineViewModel: NSObject {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let fetchedResultsController: FeedFetchedResultsController
|
let fetchedResultsController: FeedFetchedResultsController
|
||||||
let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
|
let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
@ -76,25 +77,17 @@ final class HomeTimelineViewModel: NSObject {
|
||||||
|
|
||||||
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.fetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
self.fetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||||
self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context)
|
self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context)
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
fetchedResultsController.predicate = Feed.predicate(
|
||||||
.sink { [weak self] authenticationBox in
|
kind: .home,
|
||||||
guard let self = self else { return }
|
acct: .mastodon(domain: authContext.mastodonAuthenticationBox.domain, userID: authContext.mastodonAuthenticationBox.userID)
|
||||||
guard let authenticationBox = authenticationBox else {
|
)
|
||||||
self.fetchedResultsController.predicate = Feed.predicate(kind: .none, acct: .none)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.fetchedResultsController.predicate = Feed.predicate(
|
|
||||||
kind: .home,
|
|
||||||
acct: .mastodon(domain: authenticationBox.domain, userID: authenticationBox.userID)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
homeTimelineNeedRefresh
|
homeTimelineNeedRefresh
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
|
@ -131,7 +124,6 @@ extension HomeTimelineViewModel {
|
||||||
// load timeline gap
|
// load timeline gap
|
||||||
func loadMore(item: StatusItem) async {
|
func loadMore(item: StatusItem) async {
|
||||||
guard case let .feedLoader(record) = item else { return }
|
guard case let .feedLoader(record) = item else { return }
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let diffableDataSource = diffableDataSource else { return }
|
guard let diffableDataSource = diffableDataSource else { return }
|
||||||
var snapshot = diffableDataSource.snapshot()
|
var snapshot = diffableDataSource.snapshot()
|
||||||
|
|
||||||
|
@ -169,7 +161,7 @@ extension HomeTimelineViewModel {
|
||||||
let maxID = status.id
|
let maxID = status.id
|
||||||
_ = try await context.apiService.homeTimeline(
|
_ = try await context.apiService.homeTimeline(
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -147,6 +147,11 @@ extension NotificationTimelineViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension NotificationTimelineViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:NotificationTimelineViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:NotificationTimelineViewController.AutoGenerateTableViewDelegate
|
||||||
|
@ -297,9 +302,10 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
||||||
if let stauts = notification.status {
|
if let stauts = notification.status {
|
||||||
let threadViewModel = ThreadViewModel(
|
let threadViewModel = ThreadViewModel(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
|
authContext: self.viewModel.authContext,
|
||||||
optionalRoot: .root(context: .init(status: .init(objectID: stauts.objectID)))
|
optionalRoot: .root(context: .init(status: .init(objectID: stauts.objectID)))
|
||||||
)
|
)
|
||||||
self.coordinator.present(
|
_ = self.coordinator.present(
|
||||||
scene: .thread(viewModel: threadViewModel),
|
scene: .thread(viewModel: threadViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
@ -307,9 +313,10 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
||||||
} else {
|
} else {
|
||||||
let profileViewModel = ProfileViewModel(
|
let profileViewModel = ProfileViewModel(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
|
authContext: self.viewModel.authContext,
|
||||||
optionalMastodonUser: notification.account
|
optionalMastodonUser: notification.account
|
||||||
)
|
)
|
||||||
self.coordinator.present(
|
_ = self.coordinator.present(
|
||||||
scene: .profile(viewModel: profileViewModel),
|
scene: .profile(viewModel: profileViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
|
|
@ -20,6 +20,7 @@ extension NotificationTimelineViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: NotificationSection.Configuration(
|
configuration: NotificationSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
notificationTableViewCellDelegate: notificationTableViewCellDelegate,
|
notificationTableViewCellDelegate: notificationTableViewCellDelegate,
|
||||||
filterContext: .notifications,
|
filterContext: .notifications,
|
||||||
activeFilters: context.statusFilterService.$activeFilters
|
activeFilters: context.statusFilterService.$activeFilters
|
||||||
|
|
|
@ -63,11 +63,6 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
assertionFailure()
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let lastFeedRecord = viewModel.feedFetchedResultsController.records.last else {
|
guard let lastFeedRecord = viewModel.feedFetchedResultsController.records.last else {
|
||||||
stateMachine.enter(Fail.self)
|
stateMachine.enter(Fail.self)
|
||||||
|
@ -93,7 +88,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
||||||
let response = try await viewModel.context.apiService.notifications(
|
let response = try await viewModel.context.apiService.notifications(
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
let notifications = response.value
|
let notifications = response.value
|
||||||
|
|
|
@ -21,6 +21,7 @@ final class NotificationTimelineViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let scope: Scope
|
let scope: Scope
|
||||||
let feedFetchedResultsController: FeedFetchedResultsController
|
let feedFetchedResultsController: FeedFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
@ -47,28 +48,19 @@ final class NotificationTimelineViewModel {
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AppContext,
|
context: AppContext,
|
||||||
|
authContext: AuthContext,
|
||||||
scope: Scope
|
scope: Scope
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate(
|
||||||
.sink { [weak self] authenticationBox in
|
authenticationBox: authContext.mastodonAuthenticationBox,
|
||||||
guard let self = self else { return }
|
scope: scope
|
||||||
guard let authenticationBox = authenticationBox else {
|
)
|
||||||
self.feedFetchedResultsController.predicate = Feed.nonePredicate()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let predicate = NotificationTimelineViewModel.feedPredicate(
|
|
||||||
authenticationBox: authenticationBox,
|
|
||||||
scope: scope
|
|
||||||
)
|
|
||||||
self.feedFetchedResultsController.predicate = predicate
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -122,8 +114,6 @@ extension NotificationTimelineViewModel {
|
||||||
|
|
||||||
// load lastest
|
// load lastest
|
||||||
func loadLatest() async {
|
func loadLatest() async {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
isLoadingLatest = true
|
isLoadingLatest = true
|
||||||
defer { isLoadingLatest = false }
|
defer { isLoadingLatest = false }
|
||||||
|
|
||||||
|
@ -131,7 +121,7 @@ extension NotificationTimelineViewModel {
|
||||||
_ = try await context.apiService.notifications(
|
_ = try await context.apiService.notifications(
|
||||||
maxID: nil,
|
maxID: nil,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
didLoadLatest.send()
|
didLoadLatest.send()
|
||||||
|
@ -142,7 +132,6 @@ extension NotificationTimelineViewModel {
|
||||||
// load timeline gap
|
// load timeline gap
|
||||||
func loadMore(item: NotificationItem) async {
|
func loadMore(item: NotificationItem) async {
|
||||||
guard case let .feedLoader(record) = item else { return }
|
guard case let .feedLoader(record) = item else { return }
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
|
|
||||||
let managedObjectContext = context.managedObjectContext
|
let managedObjectContext = context.managedObjectContext
|
||||||
let key = "LoadMore@\(record.objectID)"
|
let key = "LoadMore@\(record.objectID)"
|
||||||
|
@ -163,7 +152,7 @@ extension NotificationTimelineViewModel {
|
||||||
_ = try await context.apiService.notifications(
|
_ = try await context.apiService.notifications(
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch more failure: \(error.localizedDescription)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch more failure: \(error.localizedDescription)")
|
||||||
|
|
|
@ -24,7 +24,7 @@ final class NotificationViewController: TabmanViewController, NeedsDependency {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
var observations = Set<NSKeyValueObservation>()
|
var observations = Set<NSKeyValueObservation>()
|
||||||
|
|
||||||
private(set) lazy var viewModel = NotificationViewModel(context: context)
|
var viewModel: NotificationViewModel!
|
||||||
|
|
||||||
let pageSegmentedControl = UISegmentedControl()
|
let pageSegmentedControl = UISegmentedControl()
|
||||||
|
|
||||||
|
@ -154,6 +154,7 @@ extension NotificationViewController {
|
||||||
viewController.coordinator = coordinator
|
viewController.coordinator = coordinator
|
||||||
viewController.viewModel = NotificationTimelineViewModel(
|
viewController.viewModel = NotificationTimelineViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: viewModel.authContext,
|
||||||
scope: scope
|
scope: scope
|
||||||
)
|
)
|
||||||
return viewController
|
return viewController
|
||||||
|
|
|
@ -19,6 +19,7 @@ final class NotificationViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let viewDidLoad = PassthroughSubject<Void, Never>()
|
let viewDidLoad = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
// output
|
// output
|
||||||
|
@ -27,8 +28,9 @@ final class NotificationViewModel {
|
||||||
@Published var currentPageIndex = 0
|
@Published var currentPageIndex = 0
|
||||||
|
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
// end init
|
// end init
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,9 +182,13 @@ extension MastodonPickServerViewController {
|
||||||
|
|
||||||
authenticationViewModel
|
authenticationViewModel
|
||||||
.authenticated
|
.authenticated
|
||||||
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
.asyncMap { domain, user -> Result<Bool, Error> in
|
||||||
guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() }
|
do {
|
||||||
return self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
|
let result = try await self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
|
||||||
|
return .success(result)
|
||||||
|
} catch {
|
||||||
|
return .failure(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] result in
|
.sink { [weak self] result in
|
||||||
|
|
|
@ -144,7 +144,7 @@ extension WelcomeViewController {
|
||||||
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
|
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
|
||||||
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
|
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
viewModel.needsShowDismissEntry
|
viewModel.$needsShowDismissEntry
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] needsShowDismissEntry in
|
.sink { [weak self] needsShowDismissEntry in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
|
@ -17,15 +17,14 @@ final class WelcomeViewModel {
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
|
||||||
// output
|
// output
|
||||||
let needsShowDismissEntry = CurrentValueSubject<Bool, Never>(false)
|
@Published var needsShowDismissEntry = false
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
context.authenticationService.mastodonAuthentications
|
context.authenticationService.$mastodonAuthenticationBoxes
|
||||||
.map { !$0.isEmpty }
|
.map { !$0.isEmpty }
|
||||||
.assign(to: \.value, on: needsShowDismissEntry)
|
.assign(to: &$needsShowDismissEntry)
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,11 @@ extension BookmarkViewController: UITableViewDelegate, AutoGenerateTableViewDele
|
||||||
// MARK: - StatusTableViewCellDelegate
|
// MARK: - StatusTableViewCellDelegate
|
||||||
extension BookmarkViewController: StatusTableViewCellDelegate { }
|
extension BookmarkViewController: StatusTableViewCellDelegate { }
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension BookmarkViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
extension BookmarkViewController {
|
extension BookmarkViewController {
|
||||||
override var keyCommands: [UIKeyCommand]? {
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
return navigationKeyCommands + statusNavigationKeyCommands
|
return navigationKeyCommands + statusNavigationKeyCommands
|
||||||
|
|
|
@ -17,6 +17,7 @@ extension BookmarkViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||||
filterContext: .none,
|
filterContext: .none,
|
||||||
|
|
|
@ -51,7 +51,7 @@ extension BookmarkViewModel.State {
|
||||||
guard let viewModel = viewModel else { return false }
|
guard let viewModel = viewModel else { return false }
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is Reloading.Type:
|
case is Reloading.Type:
|
||||||
return viewModel.activeMastodonAuthenticationBox.value != nil
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -134,20 +134,15 @@ extension BookmarkViewModel.State {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if previousState is Reloading {
|
if previousState is Reloading {
|
||||||
maxID = nil
|
maxID = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.bookmarkedStatuses(
|
let response = try await viewModel.context.apiService.bookmarkedStatuses(
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
var hasNewStatusesAppend = false
|
var hasNewStatusesAppend = false
|
||||||
|
|
|
@ -18,7 +18,8 @@ final class BookmarkViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let activeMastodonAuthenticationBox: CurrentValueSubject<MastodonAuthenticationBox?, Never>
|
let authContext: AuthContext
|
||||||
|
|
||||||
let statusFetchedResultsController: StatusFetchedResultsController
|
let statusFetchedResultsController: StatusFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
|
||||||
|
@ -37,23 +38,14 @@ final class BookmarkViewModel {
|
||||||
return stateMachine
|
return stateMachine
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.activeMastodonAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
|
self.authContext = authContext
|
||||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalTweetPredicate: nil
|
additionalTweetPredicate: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
|
||||||
.assign(to: \.value, on: activeMastodonAuthenticationBox)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
activeMastodonAuthenticationBox
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import MastodonCore
|
||||||
|
|
||||||
final class CachedProfileViewModel: ProfileViewModel {
|
final class CachedProfileViewModel: ProfileViewModel {
|
||||||
|
|
||||||
init(context: AppContext, mastodonUser: MastodonUser) {
|
init(context: AppContext, authContext: AuthContext, mastodonUser: MastodonUser) {
|
||||||
super.init(context: context, optionalMastodonUser: mastodonUser)
|
super.init(context: context, authContext: authContext, optionalMastodonUser: mastodonUser)
|
||||||
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Profile] user[\(mastodonUser.id)] profile: \(mastodonUser.acctWithDomain)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Profile] user[\(mastodonUser.id)] profile: \(mastodonUser.acctWithDomain)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,13 @@ extension FamiliarFollowersViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension FamiliarFollowersViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext {
|
||||||
|
viewModel.authContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension FamiliarFollowersViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension FamiliarFollowersViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:FamiliarFollowersViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:FamiliarFollowersViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -17,6 +17,7 @@ final class FamiliarFollowersViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
let userFetchedResultsController: UserFetchedResultsController
|
let userFetchedResultsController: UserFetchedResultsController
|
||||||
|
|
||||||
@Published var familiarFollowers: Mastodon.Entity.FamiliarFollowers?
|
@Published var familiarFollowers: Mastodon.Entity.FamiliarFollowers?
|
||||||
|
@ -24,20 +25,16 @@ final class FamiliarFollowersViewModel {
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.userFetchedResultsController = UserFetchedResultsController(
|
self.userFetchedResultsController = UserFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalPredicate: nil
|
additionalPredicate: nil
|
||||||
)
|
)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: userFetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
$familiarFollowers
|
$familiarFollowers
|
||||||
.map { familiarFollowers -> [MastodonUser.ID] in
|
.map { familiarFollowers -> [MastodonUser.ID] in
|
||||||
guard let familiarFollowers = familiarFollowers else { return [] }
|
guard let familiarFollowers = familiarFollowers else { return [] }
|
||||||
|
|
|
@ -104,6 +104,11 @@ extension FavoriteViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension FavoriteViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension FavoriteViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension FavoriteViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:FavoriteViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:FavoriteViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -17,6 +17,7 @@ extension FavoriteViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||||
filterContext: .none,
|
filterContext: .none,
|
||||||
|
|
|
@ -51,7 +51,7 @@ extension FavoriteViewModel.State {
|
||||||
guard let viewModel = viewModel else { return false }
|
guard let viewModel = viewModel else { return false }
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is Reloading.Type:
|
case is Reloading.Type:
|
||||||
return viewModel.activeMastodonAuthenticationBox.value != nil
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -134,10 +134,6 @@ extension FavoriteViewModel.State {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if previousState is Reloading {
|
if previousState is Reloading {
|
||||||
maxID = nil
|
maxID = nil
|
||||||
}
|
}
|
||||||
|
@ -147,7 +143,7 @@ extension FavoriteViewModel.State {
|
||||||
do {
|
do {
|
||||||
let response = try await viewModel.context.apiService.favoritedStatuses(
|
let response = try await viewModel.context.apiService.favoritedStatuses(
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
var hasNewStatusesAppend = false
|
var hasNewStatusesAppend = false
|
||||||
|
|
|
@ -18,7 +18,7 @@ final class FavoriteViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let activeMastodonAuthenticationBox: CurrentValueSubject<MastodonAuthenticationBox?, Never>
|
let authContext: AuthContext
|
||||||
let statusFetchedResultsController: StatusFetchedResultsController
|
let statusFetchedResultsController: StatusFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
|
||||||
|
@ -37,23 +37,14 @@ final class FavoriteViewModel {
|
||||||
return stateMachine
|
return stateMachine
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.activeMastodonAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
|
self.authContext = authContext
|
||||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: nil,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
additionalTweetPredicate: nil
|
additionalTweetPredicate: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
|
||||||
.assign(to: \.value, on: activeMastodonAuthenticationBox)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
activeMastodonAuthenticationBox
|
|
||||||
.map { $0?.domain }
|
|
||||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,15 +82,15 @@ extension FollowerListViewController {
|
||||||
|
|
||||||
// trigger user timeline loading
|
// trigger user timeline loading
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
viewModel.domain.removeDuplicates().eraseToAnyPublisher(),
|
viewModel.$domain.removeDuplicates(),
|
||||||
viewModel.userID.removeDuplicates().eraseToAnyPublisher()
|
viewModel.$userID.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 }
|
||||||
self.viewModel.stateMachine.enter(FollowerListViewModel.State.Reloading.self)
|
self.viewModel.stateMachine.enter(FollowerListViewModel.State.Reloading.self)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
@ -101,6 +101,12 @@ extension FollowerListViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension FollowerListViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension FollowerListViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension FollowerListViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:FollowerListViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:FollowerListViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -50,10 +50,10 @@ extension FollowerListViewModel {
|
||||||
case is State.Idle, is State.Loading, is State.Fail:
|
case is State.Idle, is State.Loading, is State.Fail:
|
||||||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
case is State.NoMore:
|
case is State.NoMore:
|
||||||
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value,
|
guard let userID = self.userID,
|
||||||
let userID = self.userID.value,
|
userID != self.authContext.mastodonAuthenticationBox.userID
|
||||||
userID != activeMastodonAuthenticationBox.userID
|
|
||||||
else { break }
|
else { break }
|
||||||
|
// display hint footer exclude self
|
||||||
let text = L10n.Scene.Follower.footer
|
let text = L10n.Scene.Follower.footer
|
||||||
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -51,7 +51,7 @@ extension FollowerListViewModel.State {
|
||||||
guard let viewModel = viewModel else { return false }
|
guard let viewModel = viewModel else { return false }
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is Reloading.Type:
|
case is Reloading.Type:
|
||||||
return viewModel.userID.value != nil
|
return viewModel.userID != nil
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -139,12 +139,7 @@ extension FollowerListViewModel.State {
|
||||||
|
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
guard let userID = viewModel.userID.value, !userID.isEmpty else {
|
guard let userID = viewModel.userID, !userID.isEmpty else {
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
stateMachine.enter(Fail.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -154,7 +149,7 @@ extension FollowerListViewModel.State {
|
||||||
let response = try await viewModel.context.apiService.followers(
|
let response = try await viewModel.context.apiService.followers(
|
||||||
userID: userID,
|
userID: userID,
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count) followers")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count) followers")
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,13 @@ final class FollowerListViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let domain: CurrentValueSubject<String?, Never>
|
let authContext: AuthContext
|
||||||
let userID: CurrentValueSubject<String?, Never>
|
|
||||||
let userFetchedResultsController: UserFetchedResultsController
|
let userFetchedResultsController: UserFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
|
||||||
|
@Published var domain: String?
|
||||||
|
@Published var userID: String?
|
||||||
|
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||||
private(set) lazy var stateMachine: GKStateMachine = {
|
private(set) lazy var stateMachine: GKStateMachine = {
|
||||||
|
@ -40,16 +42,21 @@ final class FollowerListViewModel {
|
||||||
return stateMachine
|
return stateMachine
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(context: AppContext, domain: String?, userID: String?) {
|
init(
|
||||||
|
context: AppContext,
|
||||||
|
authContext: AuthContext,
|
||||||
|
domain: String?,
|
||||||
|
userID: String?
|
||||||
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.userFetchedResultsController = UserFetchedResultsController(
|
self.userFetchedResultsController = UserFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
additionalPredicate: nil
|
additionalPredicate: nil
|
||||||
)
|
)
|
||||||
self.domain = CurrentValueSubject(domain)
|
self.domain = domain
|
||||||
self.userID = CurrentValueSubject(userID)
|
self.userID = userID
|
||||||
// super.init()
|
// end init
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,8 @@ extension FollowingListViewController {
|
||||||
|
|
||||||
// trigger user timeline loading
|
// trigger user timeline loading
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
viewModel.domain.removeDuplicates().eraseToAnyPublisher(),
|
viewModel.$domain.removeDuplicates(),
|
||||||
viewModel.userID.removeDuplicates().eraseToAnyPublisher()
|
viewModel.$userID.removeDuplicates()
|
||||||
)
|
)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
|
@ -101,6 +101,11 @@ extension FollowingListViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension FollowingListViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension FollowingListViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension FollowingListViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:FollowingListViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:FollowingListViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
|
import MastodonCore
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
extension FollowingListViewModel {
|
extension FollowingListViewModel {
|
||||||
|
@ -44,23 +45,23 @@ extension FollowingListViewModel {
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
let items = records.map { UserItem.user(record: $0) }
|
let items = records.map { UserItem.user(record: $0) }
|
||||||
snapshot.appendItems(items, toSection: .main)
|
snapshot.appendItems(items, toSection: .main)
|
||||||
|
|
||||||
if let currentState = self.stateMachine.currentState {
|
if let currentState = self.stateMachine.currentState {
|
||||||
switch currentState {
|
switch currentState {
|
||||||
case is State.Idle, is State.Loading, is State.Fail:
|
case is State.Idle, is State.Loading, is State.Fail:
|
||||||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
case is State.NoMore:
|
case is State.NoMore:
|
||||||
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value,
|
guard let userID = self.userID,
|
||||||
let userID = self.userID.value,
|
userID != self.authContext.mastodonAuthenticationBox.userID
|
||||||
userID != activeMastodonAuthenticationBox.userID
|
|
||||||
else { break }
|
else { break }
|
||||||
|
// display footer exclude self
|
||||||
let text = L10n.Scene.Following.footer
|
let text = L10n.Scene.Following.footer
|
||||||
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diffableDataSource.apply(snapshot, animatingDifferences: false)
|
diffableDataSource.apply(snapshot, animatingDifferences: false)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -50,7 +50,7 @@ extension FollowingListViewModel.State {
|
||||||
guard let viewModel = viewModel else { return false }
|
guard let viewModel = viewModel else { return false }
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is Reloading.Type:
|
case is Reloading.Type:
|
||||||
return viewModel.userID.value != nil
|
return viewModel.userID != nil
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -138,12 +138,7 @@ extension FollowingListViewModel.State {
|
||||||
|
|
||||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
guard let userID = viewModel.userID.value, !userID.isEmpty else {
|
guard let userID = viewModel.userID, !userID.isEmpty else {
|
||||||
stateMachine.enter(Fail.self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
stateMachine.enter(Fail.self)
|
stateMachine.enter(Fail.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -153,7 +148,7 @@ extension FollowingListViewModel.State {
|
||||||
let response = try await viewModel.context.apiService.following(
|
let response = try await viewModel.context.apiService.following(
|
||||||
userID: userID,
|
userID: userID,
|
||||||
maxID: maxID,
|
maxID: maxID,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count)")
|
||||||
|
|
|
@ -19,11 +19,13 @@ final class FollowingListViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let domain: CurrentValueSubject<String?, Never>
|
let authContext: AuthContext
|
||||||
let userID: CurrentValueSubject<String?, Never>
|
|
||||||
let userFetchedResultsController: UserFetchedResultsController
|
let userFetchedResultsController: UserFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
|
|
||||||
|
@Published var domain: String?
|
||||||
|
@Published var userID: String?
|
||||||
|
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||||
private(set) lazy var stateMachine: GKStateMachine = {
|
private(set) lazy var stateMachine: GKStateMachine = {
|
||||||
|
@ -39,15 +41,21 @@ final class FollowingListViewModel {
|
||||||
return stateMachine
|
return stateMachine
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(context: AppContext, domain: String?, userID: String?) {
|
init(
|
||||||
|
context: AppContext,
|
||||||
|
authContext: AuthContext,
|
||||||
|
domain: String?,
|
||||||
|
userID: String?
|
||||||
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.userFetchedResultsController = UserFetchedResultsController(
|
self.userFetchedResultsController = UserFetchedResultsController(
|
||||||
managedObjectContext: context.managedObjectContext,
|
managedObjectContext: context.managedObjectContext,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
additionalPredicate: nil
|
additionalPredicate: nil
|
||||||
)
|
)
|
||||||
self.domain = CurrentValueSubject(domain)
|
self.domain = domain
|
||||||
self.userID = CurrentValueSubject(userID)
|
self.userID = userID
|
||||||
// super.init()
|
// super.init()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,10 +332,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
||||||
else { return }
|
else { return }
|
||||||
let followerListViewModel = FollowerListViewModel(
|
let followerListViewModel = FollowerListViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: viewModel.authContext,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
userID: userID
|
userID: userID
|
||||||
)
|
)
|
||||||
coordinator.present(
|
_ = coordinator.present(
|
||||||
scene: .follower(viewModel: followerListViewModel),
|
scene: .follower(viewModel: followerListViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
@ -346,10 +347,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
||||||
else { return }
|
else { return }
|
||||||
let followingListViewModel = FollowingListViewModel(
|
let followingListViewModel = FollowingListViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: viewModel.authContext,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
userID: userID
|
userID: userID
|
||||||
)
|
)
|
||||||
coordinator.present(
|
_ = coordinator.present(
|
||||||
scene: .following(viewModel: followingListViewModel),
|
scene: .following(viewModel: followingListViewModel),
|
||||||
from: self,
|
from: self,
|
||||||
transition: .show
|
transition: .show
|
||||||
|
|
|
@ -24,6 +24,8 @@ final class ProfileHeaderViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
|
|
||||||
@Published var user: MastodonUser?
|
@Published var user: MastodonUser?
|
||||||
@Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none
|
@Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none
|
||||||
|
|
||||||
|
@ -41,8 +43,9 @@ final class ProfileHeaderViewModel {
|
||||||
@Published var isTitleViewDisplaying = false
|
@Published var isTitleViewDisplaying = false
|
||||||
@Published var isTitleViewContentOffsetSet = false
|
@Published var isTitleViewContentOffsetSet = false
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
|
|
||||||
$accountForEdit
|
$accountForEdit
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
|
|
@ -15,10 +15,12 @@ import MastodonSDK
|
||||||
|
|
||||||
final class MeProfileViewModel: ProfileViewModel {
|
final class MeProfileViewModel: ProfileViewModel {
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext, authContext: AuthContext) {
|
||||||
|
let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||||
super.init(
|
super.init(
|
||||||
context: context,
|
context: context,
|
||||||
optionalMastodonUser: context.authenticationService.activeMastodonAuthentication.value?.user
|
authContext: authContext,
|
||||||
|
optionalMastodonUser: user
|
||||||
)
|
)
|
||||||
|
|
||||||
$me
|
$me
|
||||||
|
|
|
@ -111,7 +111,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
|
||||||
let viewController = ProfileHeaderViewController()
|
let viewController = ProfileHeaderViewController()
|
||||||
viewController.context = context
|
viewController.context = context
|
||||||
viewController.coordinator = coordinator
|
viewController.coordinator = coordinator
|
||||||
viewController.viewModel = ProfileHeaderViewModel(context: context)
|
viewController.viewModel = ProfileHeaderViewModel(context: context, authContext: viewModel.authContext)
|
||||||
return viewController
|
return viewController
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -460,14 +460,14 @@ extension ProfileViewController {
|
||||||
switch meta {
|
switch meta {
|
||||||
case .url(_, _, let url, _):
|
case .url(_, _, let url, _):
|
||||||
guard let url = URL(string: url) else { return }
|
guard let url = URL(string: url) else { return }
|
||||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
_ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||||
case .mention(_, _, let userInfo):
|
case .mention(_, _, let userInfo):
|
||||||
guard let href = userInfo?["href"] as? String,
|
guard let href = userInfo?["href"] as? String,
|
||||||
let url = URL(string: href) else { return }
|
let url = URL(string: href) else { return }
|
||||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
_ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||||
case .hashtag(_, let hashtag, _):
|
case .hashtag(_, let hashtag, _):
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag)
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: hashtag)
|
||||||
coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
_ = coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
||||||
case .email, .emoji:
|
case .email, .emoji:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -485,7 +485,7 @@ extension ProfileViewController {
|
||||||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard let setting = context.settingService.currentSetting.value else { return }
|
guard let setting = context.settingService.currentSetting.value else { return }
|
||||||
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
|
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
|
||||||
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,24 +513,23 @@ extension ProfileViewController {
|
||||||
|
|
||||||
@objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
@objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
let favoriteViewModel = FavoriteViewModel(context: context)
|
let favoriteViewModel = FavoriteViewModel(context: context, authContext: viewModel.authContext)
|
||||||
coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
|
coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func bookmarkBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
@objc private func bookmarkBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
let bookmarkViewModel = BookmarkViewModel(context: context)
|
let bookmarkViewModel = BookmarkViewModel(context: context, authContext: viewModel.authContext)
|
||||||
coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
|
coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let mastodonUser = viewModel.user else { return }
|
guard let mastodonUser = viewModel.user else { return }
|
||||||
let composeViewModel = ComposeViewModel(
|
let composeViewModel = ComposeViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
composeKind: .mention(user: .init(objectID: mastodonUser.objectID)),
|
composeKind: .mention(user: .init(objectID: mastodonUser.objectID)),
|
||||||
authenticationBox: authenticationBox
|
authContext: viewModel.authContext
|
||||||
)
|
)
|
||||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
|
@ -671,6 +670,11 @@ extension ProfileViewController: TabBarPagerDataSource {
|
||||||
//
|
//
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension ProfileViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - ProfileHeaderViewControllerDelegate
|
// MARK: - ProfileHeaderViewControllerDelegate
|
||||||
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
func profileHeaderViewController(
|
func profileHeaderViewController(
|
||||||
|
@ -760,16 +764,13 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
case .follow, .request, .pending, .following:
|
case .follow, .request, .pending, .following:
|
||||||
guard let user = viewModel.user else { return }
|
guard let user = viewModel.user else { return }
|
||||||
let reocrd = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
let reocrd = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
Task {
|
Task {
|
||||||
try await DataSourceFacade.responseToUserFollowAction(
|
try await DataSourceFacade.responseToUserFollowAction(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
user: reocrd,
|
user: reocrd
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case .muting:
|
case .muting:
|
||||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let user = viewModel.user else { return }
|
guard let user = viewModel.user else { return }
|
||||||
let name = user.displayNameWithFallback
|
let name = user.displayNameWithFallback
|
||||||
|
|
||||||
|
@ -784,8 +785,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
Task {
|
Task {
|
||||||
try await DataSourceFacade.responseToUserMuteAction(
|
try await DataSourceFacade.responseToUserMuteAction(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
user: record,
|
user: record
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -794,7 +794,6 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
alertController.addAction(cancelAction)
|
alertController.addAction(cancelAction)
|
||||||
present(alertController, animated: true, completion: nil)
|
present(alertController, animated: true, completion: nil)
|
||||||
case .blocking:
|
case .blocking:
|
||||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let user = viewModel.user else { return }
|
guard let user = viewModel.user else { return }
|
||||||
let name = user.displayNameWithFallback
|
let name = user.displayNameWithFallback
|
||||||
|
|
||||||
|
@ -809,8 +808,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
Task {
|
Task {
|
||||||
try await DataSourceFacade.responseToUserBlockAction(
|
try await DataSourceFacade.responseToUserBlockAction(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
user: record,
|
user: record
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -852,7 +850,6 @@ extension ProfileViewController: ProfileAboutViewControllerDelegate {
|
||||||
// MARK: - MastodonMenuDelegate
|
// MARK: - MastodonMenuDelegate
|
||||||
extension ProfileViewController: MastodonMenuDelegate {
|
extension ProfileViewController: MastodonMenuDelegate {
|
||||||
func menuAction(_ action: MastodonMenu.Action) {
|
func menuAction(_ action: MastodonMenu.Action) {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
|
||||||
guard let user = viewModel.user else { return }
|
guard let user = viewModel.user else { return }
|
||||||
|
|
||||||
let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||||
|
@ -866,8 +863,7 @@ extension ProfileViewController: MastodonMenuDelegate {
|
||||||
status: nil,
|
status: nil,
|
||||||
button: nil,
|
button: nil,
|
||||||
barButtonItem: self.moreMenuBarButtonItem
|
barButtonItem: self.moreMenuBarButtonItem
|
||||||
),
|
)
|
||||||
authenticationBox: authenticationBox
|
|
||||||
)
|
)
|
||||||
} // end Task
|
} // end Task
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ class ProfileViewModel: NSObject {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
@Published var me: MastodonUser?
|
@Published var me: MastodonUser?
|
||||||
@Published var user: MastodonUser?
|
@Published var user: MastodonUser?
|
||||||
|
|
||||||
|
@ -58,21 +59,25 @@ class ProfileViewModel: NSObject {
|
||||||
// @Published var protected: Bool? = nil
|
// @Published var protected: Bool? = nil
|
||||||
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.authContext = authContext
|
||||||
self.user = mastodonUser
|
self.user = mastodonUser
|
||||||
self.postsUserTimelineViewModel = UserTimelineViewModel(
|
self.postsUserTimelineViewModel = UserTimelineViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: authContext,
|
||||||
title: L10n.Scene.Profile.SegmentedControl.posts,
|
title: L10n.Scene.Profile.SegmentedControl.posts,
|
||||||
queryFilter: .init(excludeReplies: true)
|
queryFilter: .init(excludeReplies: true)
|
||||||
)
|
)
|
||||||
self.repliesUserTimelineViewModel = UserTimelineViewModel(
|
self.repliesUserTimelineViewModel = UserTimelineViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: authContext,
|
||||||
title: L10n.Scene.Profile.SegmentedControl.postsAndReplies,
|
title: L10n.Scene.Profile.SegmentedControl.postsAndReplies,
|
||||||
queryFilter: .init(excludeReplies: false)
|
queryFilter: .init(excludeReplies: false)
|
||||||
)
|
)
|
||||||
self.mediaUserTimelineViewModel = UserTimelineViewModel(
|
self.mediaUserTimelineViewModel = UserTimelineViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
|
authContext: authContext,
|
||||||
title: L10n.Scene.Profile.SegmentedControl.media,
|
title: L10n.Scene.Profile.SegmentedControl.media,
|
||||||
queryFilter: .init(onlyMedia: true)
|
queryFilter: .init(onlyMedia: true)
|
||||||
)
|
)
|
||||||
|
@ -80,13 +85,7 @@ class ProfileViewModel: NSObject {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
// bind me
|
// bind me
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
self.me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] authenticationBox in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.me = authenticationBox?.authenticationRecord.object(in: context.managedObjectContext)?.user
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
$me
|
$me
|
||||||
.assign(to: \.me, on: relationshipViewModel)
|
.assign(to: \.me, on: relationshipViewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
@ -132,21 +131,18 @@ class ProfileViewModel: NSObject {
|
||||||
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
||||||
|
|
||||||
// observe friendship
|
// observe friendship
|
||||||
Publishers.CombineLatest3(
|
Publishers.CombineLatest(
|
||||||
userRecord,
|
userRecord,
|
||||||
context.authenticationService.activeMastodonAuthenticationBox,
|
|
||||||
pendingRetryPublisher
|
pendingRetryPublisher
|
||||||
)
|
)
|
||||||
.sink { [weak self] userRecord, authenticationBox, _ in
|
.sink { [weak self] userRecord, _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let userRecord = userRecord,
|
guard let userRecord = userRecord else { return }
|
||||||
let authenticationBox = authenticationBox
|
|
||||||
else { return }
|
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let response = try await self.updateRelationship(
|
let response = try await self.updateRelationship(
|
||||||
record: userRecord,
|
record: userRecord,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: self.authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
// there are seconds delay after request follow before requested -> following. Query again when needs
|
// there are seconds delay after request follow before requested -> following. Query again when needs
|
||||||
guard let relationship = response.value.first else { return }
|
guard let relationship = response.value.first else { return }
|
||||||
|
@ -216,10 +212,7 @@ extension ProfileViewModel {
|
||||||
headerProfileInfo: ProfileHeaderViewModel.ProfileInfo,
|
headerProfileInfo: ProfileHeaderViewModel.ProfileInfo,
|
||||||
aboutProfileInfo: ProfileAboutViewModel.ProfileInfo
|
aboutProfileInfo: ProfileAboutViewModel.ProfileInfo
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
let authenticationBox = authContext.mastodonAuthenticationBox
|
||||||
throw APIService.APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let domain = authenticationBox.domain
|
let domain = authenticationBox.domain
|
||||||
let authorization = authenticationBox.userAuthorization
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,11 @@ import MastodonCore
|
||||||
|
|
||||||
final class RemoteProfileViewModel: ProfileViewModel {
|
final class RemoteProfileViewModel: ProfileViewModel {
|
||||||
|
|
||||||
init(context: AppContext, userID: Mastodon.Entity.Account.ID) {
|
init(context: AppContext, authContext: AuthContext, userID: Mastodon.Entity.Account.ID) {
|
||||||
super.init(context: context, optionalMastodonUser: nil)
|
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||||
|
|
||||||
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
let domain = authContext.mastodonAuthenticationBox.domain
|
||||||
return
|
let authorization = authContext.mastodonAuthenticationBox.userAuthorization
|
||||||
}
|
|
||||||
let domain = activeMastodonAuthenticationBox.domain
|
|
||||||
let authorization = activeMastodonAuthenticationBox.userAuthorization
|
|
||||||
Just(userID)
|
Just(userID)
|
||||||
.asyncMap { userID in
|
.asyncMap { userID in
|
||||||
try await context.apiService.accountInfo(
|
try await context.apiService.accountInfo(
|
||||||
|
@ -54,23 +51,19 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AppContext, notificationID: Mastodon.Entity.Notification.ID) {
|
init(context: AppContext, authContext: AuthContext, notificationID: Mastodon.Entity.Notification.ID) {
|
||||||
super.init(context: context, optionalMastodonUser: nil)
|
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||||
|
|
||||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
let response = try await context.apiService.notification(
|
let response = try await context.apiService.notification(
|
||||||
notificationID: notificationID,
|
notificationID: notificationID,
|
||||||
authenticationBox: authenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
let userID = response.value.account.id
|
let userID = response.value.account.id
|
||||||
|
|
||||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||||
let request = MastodonUser.sortedFetchRequest
|
let request = MastodonUser.sortedFetchRequest
|
||||||
request.predicate = MastodonUser.predicate(domain: authenticationBox.domain, id: userID)
|
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||||
request.fetchLimit = 1
|
request.fetchLimit = 1
|
||||||
return context.managedObjectContext.safeFetch(request).first
|
return context.managedObjectContext.safeFetch(request).first
|
||||||
}
|
}
|
||||||
|
@ -79,14 +72,14 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
||||||
self.user = user
|
self.user = user
|
||||||
} else {
|
} else {
|
||||||
_ = try await context.apiService.accountInfo(
|
_ = try await context.apiService.accountInfo(
|
||||||
domain: authenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
userID: userID,
|
userID: userID,
|
||||||
authorization: authenticationBox.userAuthorization
|
authorization: authContext.mastodonAuthenticationBox.userAuthorization
|
||||||
)
|
)
|
||||||
|
|
||||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||||
let request = MastodonUser.sortedFetchRequest
|
let request = MastodonUser.sortedFetchRequest
|
||||||
request.predicate = MastodonUser.predicate(domain: authenticationBox.domain, id: userID)
|
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||||
request.fetchLimit = 1
|
request.fetchLimit = 1
|
||||||
return context.managedObjectContext.safeFetch(request).first
|
return context.managedObjectContext.safeFetch(request).first
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,11 @@ extension UserTimelineViewController: CellFrameCacheContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthContextProvider
|
||||||
|
extension UserTimelineViewController: AuthContextProvider {
|
||||||
|
var authContext: AuthContext { viewModel.authContext }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension UserTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
extension UserTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||||
// sourcery:inline:UserTimelineViewController.AutoGenerateTableViewDelegate
|
// sourcery:inline:UserTimelineViewController.AutoGenerateTableViewDelegate
|
||||||
|
|
|
@ -18,6 +18,7 @@ extension UserTimelineViewModel {
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
context: context,
|
context: context,
|
||||||
configuration: StatusSection.Configuration(
|
configuration: StatusSection.Configuration(
|
||||||
|
authContext: authContext,
|
||||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||||
filterContext: .none,
|
filterContext: .none,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue