Merge remote-tracking branch 'upstream/develop' into compose-a11y
This commit is contained in:
commit
eef012678b
9
.github/scripts/build-release.sh
vendored
9
.github/scripts/build-release.sh
vendored
@ -20,7 +20,6 @@ ARTIFACT_PATH=${RESULT_PATH:-${BUILD_DIR}/Artifacts}
|
||||
RESULT_BUNDLE_PATH="${ARTIFACT_PATH}/${SCHEME}.xcresult"
|
||||
ARCHIVE_PATH=${ARCHIVE_PATH:-${BUILD_DIR}/Archives/${SCHEME}.xcarchive}
|
||||
DERIVED_DATA_PATH=${DERIVED_DATA_PATH:-${BUILD_DIR}/DerivedData}
|
||||
CURRENT_PROJECT_VERSION=${BUILD_NUMBER:-0}
|
||||
EXPORT_OPTIONS_FILE=".github/support/ExportOptions.plist"
|
||||
|
||||
WORK_DIR=$(pwd)
|
||||
@ -31,7 +30,13 @@ rm -rf "${RESULT_BUNDLE_PATH}"
|
||||
|
||||
rm -rf "${API_PRIVATE_KEYS_PATH}"
|
||||
mkdir -p "${API_PRIVATE_KEYS_PATH}"
|
||||
echo -n "${ENV_API_PRIVATE_KEY}" | base64 --decode > "${API_KEY_FILE}"
|
||||
echo -n "${ENV_API_PRIVATE_KEY_BASE64}" | base64 --decode > "${API_KEY_FILE}"
|
||||
|
||||
BUILD_NUMBER=$(app-store-connect get-latest-testflight-build-number $ENV_APP_ID --issuer-id $ENV_ISSUER_ID --key-id $ENV_API_KEY_ID --private-key @file:$API_KEY_FILE)
|
||||
BUILD_NUMBER=$((BUILD_NUMBER+1))
|
||||
CURRENT_PROJECT_VERSION=${BUILD_NUMBER:-0}
|
||||
|
||||
agvtool new-version -all $CURRENT_PROJECT_VERSION
|
||||
|
||||
xcrun xcodebuild clean \
|
||||
-workspace "${WORKSPACE}" \
|
||||
|
2
.github/support/ExportOptions.plist
vendored
2
.github/support/ExportOptions.plist
vendored
@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store</string>
|
||||
<key>manageAppVersionAndBuildNumber</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
12
.github/workflows/develop-build.yml
vendored
12
.github/workflows/develop-build.yml
vendored
@ -20,6 +20,14 @@ jobs:
|
||||
NotificationEndpointRelease: ${{ secrets.NotificationEndpointRelease }}
|
||||
run: exec ./.github/scripts/setup.sh
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: |
|
||||
pip3 install codemagic-cli-tools
|
||||
- run: |
|
||||
codemagic-cli-tools --version || true
|
||||
|
||||
- name: Import Code-Signing Certificates
|
||||
uses: Apple-Actions/import-codesign-certs@v1 # https://github.com/Apple-Actions/import-codesign-certs
|
||||
with:
|
||||
@ -37,9 +45,11 @@ jobs:
|
||||
|
||||
- name: build
|
||||
env:
|
||||
ENV_APP_ID: ${{ secrets.APP_ID }}
|
||||
ENV_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
|
||||
ENV_API_KEY_ID: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
ENV_API_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_KEY_BASE64 }}
|
||||
ENV_API_PRIVATE_KEY: ${{ secrets.APPSTORE_PRIVATE_KEY }}
|
||||
ENV_API_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_KEY_BASE64 }}
|
||||
run: exec ./.github/scripts/build-release.sh
|
||||
|
||||
- name: Upload TestFlight Build
|
||||
|
@ -1,30 +1,30 @@
|
||||
# Contributing
|
||||
|
||||
- File the issue for bug report and feature request
|
||||
- File an issue to report a bug or feature request
|
||||
- Translate the project in our [Crowdin](https://crowdin.com/project/mastodon-for-ios) project
|
||||
- Make the Pull Request to contribute
|
||||
|
||||
## Bug Report
|
||||
File the issue about the bug. Make sure you are installing the latest version app from TestFlight or App Store.
|
||||
File an issue about the bug or feature request. Make sure you are installing the latest version of the app from TestFlight or App Store.
|
||||
|
||||
## Translation
|
||||
[![Crowdin](https://badges.crowdin.net/mastodon-for-ios/localized.svg)](https://crowdin.com/project/mastodon-for-ios)
|
||||
|
||||
The translation will update regularly. Please request language if not listed via issue.
|
||||
The translation will update regularly. Please request the language if it is not listed via an issue.
|
||||
|
||||
## Pull Request
|
||||
|
||||
You can make a pull request directly with small block code changes for bugfix or feature implementations. Before making a pull request with hundred lines of changes to this repository, please first discuss the change you wish to make via issue.
|
||||
You can create a pull request directly with small block code changes for bugfix or feature implementations. Before making a pull request with hundred lines of changes to this repository, please first discuss the change you wish to make via an issue.
|
||||
|
||||
Also, there are lots of existing feature request issues that could be a good-first-issue discussing place.
|
||||
|
||||
Follow the git-flow pattern to make your pull request.
|
||||
|
||||
1. Ensure you are checkout on the `develop` branch.
|
||||
2. Write your codes and test them on **iPad and iPhone**.
|
||||
3. Merge the `develop` into your branch then make a Pull Request. Please merge the branch and resolve any conflicts when the `develop` updates. **Do not force push your codes.**
|
||||
4. Make sure the permission for your folk is open to the reviewer. Code style fix, conflict resolution, and other changes may be committed by the reviewer directly.
|
||||
1. Ensure you have started a new branch based on the `develop` branch.
|
||||
2. Write your changes and test them on **iPad and iPhone**.
|
||||
3. Merge the `develop` branch into your branch then make a Pull Request. Please merge the branch and resolve any conflicts if `develop` updates. **Do not force push your commits.**
|
||||
4. Make sure the permission for your fork is open to the reviewer. Code style fix, conflict resolution, and other changes may be committed by the reviewer directly.
|
||||
5. Request a code review and wait for approval. The PR will be merged when it is approved.
|
||||
|
||||
## Documentation
|
||||
The documents for this app is list under the [Documentation](../Documentation/) folder. We are also welcome contributions for documentation.
|
||||
The documentation for this app is listed under the [Documentation](../Documentation/) folder. We are also welcoming contributions for documentation.
|
||||
|
@ -100,6 +100,7 @@ GEM
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-21
|
||||
x86_64-darwin-21
|
||||
|
||||
DEPENDENCIES
|
||||
arkana
|
||||
|
@ -180,7 +180,9 @@
|
||||
"unmute": "Unmute",
|
||||
"unmute_user": "Unmute %s",
|
||||
"muted": "Muted",
|
||||
"edit_info": "Edit Info"
|
||||
"edit_info": "Edit Info",
|
||||
"show_reblogs": "Show Reblogs",
|
||||
"hide_reblogs": "Hide Reblogs"
|
||||
},
|
||||
"timeline": {
|
||||
"filtered": "Filtered",
|
||||
@ -457,6 +459,14 @@
|
||||
"confirm_unblock_user": {
|
||||
"title": "Unblock Account",
|
||||
"message": "Confirm to unblock %s"
|
||||
},
|
||||
"confirm_show_reblogs": {
|
||||
"title": "Show Reblogs",
|
||||
"message": "Confirm to show reblogs"
|
||||
},
|
||||
"confirm_hide_reblogs": {
|
||||
"title": "Hide Reblogs",
|
||||
"message": "Confirm to hide reblogs"
|
||||
}
|
||||
},
|
||||
"accessibility": {
|
||||
|
@ -3771,6 +3771,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@ -3834,6 +3835,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@ -3866,7 +3868,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||
@ -3874,6 +3875,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -3895,7 +3897,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||
@ -3903,6 +3904,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -4030,6 +4032,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@ -4067,7 +4070,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||
@ -4075,6 +4077,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -4133,7 +4136,6 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4155,7 +4157,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4178,7 +4179,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = MastodonIntent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4201,7 +4201,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = MastodonIntent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4224,7 +4223,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = MastodonIntent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4247,7 +4245,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4270,7 +4267,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4323,6 +4319,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@ -4356,7 +4353,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||
@ -4364,6 +4360,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -4421,7 +4418,6 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4443,7 +4439,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4466,7 +4461,6 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = MastodonIntent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4490,7 +4484,6 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -4513,7 +4506,6 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -189,15 +189,6 @@
|
||||
"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",
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.7</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@ -30,7 +30,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>147</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
|
@ -132,3 +132,14 @@ extension DataSourceFacade {
|
||||
}
|
||||
} // end func
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToShowHideReblogAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async throws {
|
||||
_ = try await dependency.context.apiService.toggleShowReblogs(
|
||||
for: user,
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox)
|
||||
}
|
||||
}
|
||||
|
@ -205,6 +205,45 @@ extension DataSourceFacade {
|
||||
menuContext: MenuContext
|
||||
) async throws {
|
||||
switch action {
|
||||
case .hideReblogs(let actionContext):
|
||||
let title = actionContext.showReblogs ? L10n.Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.title : L10n.Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.title
|
||||
let message = actionContext.showReblogs ? L10n.Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.message : L10n.Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.message
|
||||
|
||||
let alertController = UIAlertController(
|
||||
title: title,
|
||||
message: message,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
let actionTitle = actionContext.showReblogs ? L10n.Common.Controls.Friendship.hideReblogs : L10n.Common.Controls.Friendship.showReblogs
|
||||
let showHideReblogsAction = UIAlertAction(
|
||||
title: actionTitle,
|
||||
style: .destructive
|
||||
) { [weak dependency] _ in
|
||||
guard let dependency else { return }
|
||||
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
|
||||
guard let user = _user else { return }
|
||||
|
||||
try await DataSourceFacade.responseToShowHideReblogAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
alertController.addAction(showHideReblogsAction)
|
||||
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
dependency.present(alertController, animated: true)
|
||||
case .muteUser(let actionContext):
|
||||
let alertController = UIAlertController(
|
||||
title: actionContext.isMuting ? L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title : L10n.Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.title,
|
||||
@ -230,9 +269,9 @@ extension DataSourceFacade {
|
||||
} // end Task
|
||||
}
|
||||
alertController.addAction(confirmAction)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
||||
alertController.addAction(cancelAction)
|
||||
dependency.present(alertController, animated: true, completion: nil)
|
||||
dependency.present(alertController, animated: true)
|
||||
case .blockUser(let actionContext):
|
||||
let alertController = UIAlertController(
|
||||
title: actionContext.isBlocking ? L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title : L10n.Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.title,
|
||||
@ -258,9 +297,9 @@ extension DataSourceFacade {
|
||||
} // end Task
|
||||
}
|
||||
alertController.addAction(confirmAction)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
||||
alertController.addAction(cancelAction)
|
||||
dependency.present(alertController, animated: true, completion: nil)
|
||||
dependency.present(alertController, animated: true)
|
||||
case .reportUser:
|
||||
Task {
|
||||
guard let user = menuContext.author else { return }
|
||||
@ -349,9 +388,9 @@ extension DataSourceFacade {
|
||||
} // end Task
|
||||
}
|
||||
alertController.addAction(confirmAction)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
||||
alertController.addAction(cancelAction)
|
||||
dependency.present(alertController, animated: true, completion: nil)
|
||||
dependency.present(alertController, animated: true)
|
||||
|
||||
}
|
||||
} // end func
|
||||
@ -371,3 +410,4 @@ extension DataSourceFacade {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,12 @@ protocol ScrollViewContainer: UIViewController {
|
||||
|
||||
extension ScrollViewContainer {
|
||||
func scrollToTop(animated: Bool) {
|
||||
scrollView.scrollRectToVisible(CGRect(origin: .zero, size: CGSize(width: 1, height: 1)), animated: animated)
|
||||
scrollView.scrollToTop(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIScrollView {
|
||||
func scrollToTop(animated: Bool) {
|
||||
scrollRectToVisible(CGRect(origin: .zero, size: CGSize(width: 1, height: 1)), animated: animated)
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,13 @@ extension DiscoveryViewController: ScrollViewContainer {
|
||||
var scrollView: UIScrollView {
|
||||
return (currentViewController as? ScrollViewContainer)?.scrollView ?? UIScrollView()
|
||||
}
|
||||
func scrollToTop(animated: Bool) {
|
||||
if scrollView.contentOffset.y <= 0 {
|
||||
scrollToPage(.first, animated: animated)
|
||||
} else {
|
||||
scrollView.scrollToTop(animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DiscoveryViewController {
|
||||
|
@ -376,13 +376,22 @@ extension ProfileViewController {
|
||||
}
|
||||
let name = user.displayNameWithFallback
|
||||
let _ = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
|
||||
var menuActions: [MastodonMenu.Action] = [
|
||||
.muteUser(.init(name: name, isMuting: self.viewModel.relationshipViewModel.isMuting)),
|
||||
.blockUser(.init(name: name, isBlocking: self.viewModel.relationshipViewModel.isBlocking)),
|
||||
.reportUser(.init(name: name)),
|
||||
.shareUser(.init(name: name)),
|
||||
]
|
||||
|
||||
if let me = self.viewModel?.me, me.following.contains(user) {
|
||||
let showReblogs = me.showingReblogsBy.contains(user)
|
||||
let context = MastodonMenu.HideReblogsActionContext(showReblogs: showReblogs)
|
||||
menuActions.insert(.hideReblogs(context), at: 1)
|
||||
}
|
||||
|
||||
let menu = MastodonMenu.setupMenu(
|
||||
actions: [
|
||||
.muteUser(.init(name: name, isMuting: self.viewModel.relationshipViewModel.isMuting)),
|
||||
.blockUser(.init(name: name, isBlocking: self.viewModel.relationshipViewModel.isBlocking)),
|
||||
.reportUser(.init(name: name)),
|
||||
.shareUser(.init(name: name)),
|
||||
],
|
||||
actions: menuActions,
|
||||
delegate: self
|
||||
)
|
||||
return menu
|
||||
@ -397,7 +406,9 @@ extension ProfileViewController {
|
||||
}
|
||||
} receiveValue: { [weak self] menu in
|
||||
guard let self = self else { return }
|
||||
self.moreMenuBarButtonItem.menu = menu
|
||||
OperationQueue.main.addOperation {
|
||||
self.moreMenuBarButtonItem.menu = menu
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
@ -740,7 +751,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||
let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.EditProfileFailure.title, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
self.coordinator.present(
|
||||
_ = self.coordinator.present(
|
||||
scene: .alertController(alertController: alertController),
|
||||
from: nil,
|
||||
transition: .alertController(animated: true, completion: nil)
|
||||
@ -763,11 +774,11 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||
break
|
||||
case .follow, .request, .pending, .following:
|
||||
guard let user = viewModel.user else { return }
|
||||
let reocrd = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: self,
|
||||
user: reocrd
|
||||
user: record
|
||||
)
|
||||
}
|
||||
case .muting:
|
||||
@ -816,10 +827,8 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
case .blocked:
|
||||
case .blocked, .showReblogs, .isMyself,.followingBy, .blockingBy, .suspended, .edit, .editing, .updating:
|
||||
break
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,7 @@ extension SidebarViewModel {
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
cell.isAccessibilityElement = true
|
||||
cell.accessibilityLabel = item.title
|
||||
cell.accessibilityTraits.insert(.button)
|
||||
|
||||
self.$currentTab
|
||||
.receive(on: DispatchQueue.main)
|
||||
@ -141,6 +142,7 @@ extension SidebarViewModel {
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
cell.isAccessibilityElement = true
|
||||
cell.accessibilityLabel = item.title
|
||||
cell.accessibilityTraits.insert(.button)
|
||||
}
|
||||
|
||||
// header
|
||||
|
@ -190,6 +190,16 @@ extension SearchViewController: UISearchControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ScrollViewContainer
|
||||
extension SearchViewController: ScrollViewContainer {
|
||||
var scrollView: UIScrollView {
|
||||
discoveryViewController?.scrollView ?? UIScrollView()
|
||||
}
|
||||
func scrollToTop(animated: Bool) {
|
||||
discoveryViewController?.scrollToTop(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
//extension SearchViewController: UICollectionViewDelegate {
|
||||
// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
|
@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.7</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>147</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
|
@ -40,7 +40,6 @@ let package = Package(
|
||||
.package(url: "https://github.com/MainasuK/CommonOSLog", from: "0.1.1"),
|
||||
.package(url: "https://github.com/MainasuK/FPSIndicator.git", from: "1.0.0"),
|
||||
.package(url: "https://github.com/slackhq/PanModal.git", from: "1.2.7"),
|
||||
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"),
|
||||
.package(url: "https://github.com/TimOliver/TOCropViewController.git", from: "2.6.1"),
|
||||
.package(url: "https://github.com/TwidereProject/MetaTextKit.git", exact: "2.2.5"),
|
||||
.package(url: "https://github.com/TwidereProject/TabBarPager.git", from: "0.1.0"),
|
||||
@ -103,7 +102,6 @@ let package = Package(
|
||||
.target(
|
||||
name: "MastodonSDK",
|
||||
dependencies: [
|
||||
.product(name: "SwiftyJSON", package: "SwiftyJSON"),
|
||||
.product(name: "NIOHTTP1", package: "swift-nio"),
|
||||
]
|
||||
),
|
||||
|
@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>CoreData 3.xcdatamodel</string>
|
||||
<string>CoreData 4.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -0,0 +1,254 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21279" systemVersion="21G115" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Application" representedClassName="CoreDataStack.Application" syncable="YES">
|
||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="vapidKey" optional="YES" attributeType="String"/>
|
||||
<attribute name="website" optional="YES" attributeType="String"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="application" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="DomainBlock" representedClassName="CoreDataStack.DomainBlock" syncable="YES">
|
||||
<attribute name="blockedDomain" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="userID"/>
|
||||
<constraint value="domain"/>
|
||||
<constraint value="blockedDomain"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Emoji" representedClassName="CoreDataStack.Emoji" syncable="YES">
|
||||
<attribute name="category" optional="YES" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="shortcode" attributeType="String"/>
|
||||
<attribute name="staticURL" attributeType="String"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="Feed" representedClassName="CoreDataStack.Feed" syncable="YES">
|
||||
<attribute name="acctRaw" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hasMore" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isLoadingMore" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="kindRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="notification" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Notification" inverseName="feeds" inverseEntity="Notification"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="feeds" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Instance" representedClassName="CoreDataStack.Instance" syncable="YES">
|
||||
<attribute name="configurationRaw" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="authentications" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="instance" inverseEntity="MastodonAuthentication"/>
|
||||
</entity>
|
||||
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthentication" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="appAccessToken" attributeType="String"/>
|
||||
<attribute name="clientID" attributeType="String"/>
|
||||
<attribute name="clientSecret" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userAccessToken" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="instance" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Instance" inverseName="authentications" inverseEntity="Instance"/>
|
||||
<relationship name="user" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mastodonAuthentication" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="MastodonUser" representedClassName="CoreDataStack.MastodonUser" syncable="YES">
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" attributeType="String"/>
|
||||
<attribute name="avatarStatic" optional="YES" attributeType="String"/>
|
||||
<attribute name="bot" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="displayName" attributeType="String"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="fields" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="followersCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="header" attributeType="String"/>
|
||||
<attribute name="headerStatic" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="statusesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="suspended" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="blocking" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="blockingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="bookmarked" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="bookmarkedBy" inverseEntity="Status"/>
|
||||
<relationship name="domainBlocking" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="domainBlockingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsed" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="endorsedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="endorsed" inverseEntity="MastodonUser"/>
|
||||
<relationship name="favourite" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="favouritedBy" inverseEntity="Status"/>
|
||||
<relationship name="following" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="following" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequested" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequestedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequestedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequested" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mastodonAuthentication" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="user" inverseEntity="MastodonAuthentication"/>
|
||||
<relationship name="muted" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="mutedBy" inverseEntity="Status"/>
|
||||
<relationship name="muting" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mutingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mutingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muting" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" toMany="YES" deletionRule="Nullify" destinationEntity="Notification" inverseName="account" inverseEntity="Notification"/>
|
||||
<relationship name="pinnedStatus" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="pinnedBy" inverseEntity="Status"/>
|
||||
<relationship name="privateNotes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="to" inverseEntity="PrivateNote"/>
|
||||
<relationship name="privateNotesTo" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="from" inverseEntity="PrivateNote"/>
|
||||
<relationship name="reblogged" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="rebloggedBy" inverseEntity="Status"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" destinationEntity="SearchHistory" inverseName="account" inverseEntity="SearchHistory"/>
|
||||
<relationship name="showingReblogs" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="showingReblogsBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="showingReblogsBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="showingReblogs" inverseEntity="MastodonUser"/>
|
||||
<relationship name="statuses" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="author" inverseEntity="Status"/>
|
||||
<relationship name="votePollOptions" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="votedBy" inverseEntity="PollOption"/>
|
||||
<relationship name="votePolls" toMany="YES" deletionRule="Nullify" destinationEntity="Poll" inverseName="votedBy" inverseEntity="Poll"/>
|
||||
</entity>
|
||||
<entity name="Notification" representedClassName="CoreDataStack.Notification" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="followRequestState" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="transientFollowRequestState" optional="YES" transient="YES" attributeType="Binary"/>
|
||||
<attribute name="typeRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="notifications" inverseEntity="MastodonUser"/>
|
||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="notification" inverseEntity="Feed"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Poll" representedClassName="CoreDataStack.Poll" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="expired" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="expiresAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="isVoting" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="multiple" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="options" toMany="YES" deletionRule="Cascade" destinationEntity="PollOption" inverseName="poll" inverseEntity="PollOption"/>
|
||||
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="poll" inverseEntity="Status"/>
|
||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="PollOption" representedClassName="CoreDataStack.PollOption" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isSelected" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="title" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="votesCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="poll" maxCount="1" deletionRule="Nullify" destinationEntity="Poll" inverseName="options" inverseEntity="Poll"/>
|
||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePollOptions" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="PrivateNote" representedClassName="CoreDataStack.PrivateNote" syncable="YES">
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="from" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotesTo" inverseEntity="MastodonUser"/>
|
||||
<relationship name="to" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotes" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="SearchHistory" representedClassName="CoreDataStack.SearchHistory" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String" defaultValueString=""/>
|
||||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="searchHistories" inverseEntity="MastodonUser"/>
|
||||
<relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag" inverseName="searchHistories" inverseEntity="Tag"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="searchHistories" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Setting" representedClassName="CoreDataStack.Setting" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="preferredStaticAvatar" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="preferredStaticEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="preferredTrueBlackDarkMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="preferredUsingDefaultBrowser" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="subscriptions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Subscription" inverseName="setting" inverseEntity="Subscription"/>
|
||||
</entity>
|
||||
<entity name="Status" representedClassName="CoreDataStack.Status" syncable="YES">
|
||||
<attribute name="attachments" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="content" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="inReplyToAccountID" optional="YES" attributeType="String"/>
|
||||
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
||||
<attribute name="isSensitiveToggled" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="language" optional="YES" attributeType="String"/>
|
||||
<attribute name="mentions" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="reblogsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="repliesCount" optional="YES" attributeType="Integer 64" usesScalarValueType="NO"/>
|
||||
<attribute name="revealedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uri" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<attribute name="visibilityRaw" optional="YES" attributeType="String" elementID="visibility"/>
|
||||
<relationship name="application" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Application" inverseName="status" inverseEntity="Application"/>
|
||||
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
|
||||
<relationship name="bookmarkedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||
<relationship name="favouritedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="status" inverseEntity="Feed"/>
|
||||
<relationship name="mutedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" toMany="YES" deletionRule="Cascade" destinationEntity="Notification" inverseName="status" inverseEntity="Notification"/>
|
||||
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
|
||||
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
|
||||
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
||||
<relationship name="reblogFrom" toMany="YES" deletionRule="Cascade" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
||||
<relationship name="rebloggedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
||||
<relationship name="replyFrom" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
||||
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Cascade" destinationEntity="SearchHistory" inverseName="status" inverseEntity="SearchHistory"/>
|
||||
</entity>
|
||||
<entity name="Subscription" representedClassName="CoreDataStack.Subscription" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="endpoint" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" optional="YES" attributeType="String"/>
|
||||
<attribute name="policyRaw" attributeType="String"/>
|
||||
<attribute name="serverKey" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userToken" optional="YES" attributeType="String"/>
|
||||
<relationship name="alert" maxCount="1" deletionRule="Cascade" destinationEntity="SubscriptionAlerts" inverseName="subscription" inverseEntity="SubscriptionAlerts"/>
|
||||
<relationship name="setting" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Setting" inverseName="subscriptions" inverseEntity="Setting"/>
|
||||
</entity>
|
||||
<entity name="SubscriptionAlerts" representedClassName="CoreDataStack.SubscriptionAlerts" syncable="YES">
|
||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="favouriteRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="followRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="followRequestRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mentionRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="pollRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="subscription" maxCount="1" deletionRule="Nullify" destinationEntity="Subscription" inverseName="alert" inverseEntity="Subscription"/>
|
||||
</entity>
|
||||
<entity name="Tag" representedClassName="CoreDataStack.Tag" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="histories" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" destinationEntity="SearchHistory" inverseName="hashtag" inverseEntity="SearchHistory"/>
|
||||
</entity>
|
||||
</model>
|
@ -89,7 +89,8 @@ final public class MastodonUser: NSManagedObject {
|
||||
@NSManaged public private(set) var endorsedBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var domainBlocking: Set<MastodonUser>
|
||||
@NSManaged public private(set) var domainBlockingBy: Set<MastodonUser>
|
||||
|
||||
@NSManaged public private(set) var showingReblogs: Set<MastodonUser>
|
||||
@NSManaged public private(set) var showingReblogsBy: Set<MastodonUser>
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
@ -521,6 +522,7 @@ extension MastodonUser: AutoUpdatableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(isDomainBlocking: Bool, by mastodonUser: MastodonUser) {
|
||||
if isDomainBlocking {
|
||||
if !self.domainBlockingBy.contains(mastodonUser) {
|
||||
@ -533,4 +535,15 @@ extension MastodonUser: AutoUpdatableObject {
|
||||
}
|
||||
}
|
||||
|
||||
public func update(isShowingReblogs: Bool, by mastodonUser: MastodonUser) {
|
||||
if isShowingReblogs {
|
||||
if !self.showingReblogsBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.showingReblogsBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.showingReblogsBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.showingReblogsBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,5 +157,6 @@ extension Persistence.MastodonUser {
|
||||
user.update(isBlocking: relationship.blocking, by: me)
|
||||
relationship.domainBlocking.flatMap { user.update(isDomainBlocking: $0, by: me) }
|
||||
relationship.blockedBy.flatMap { me.update(isBlocking: $0, by: user) }
|
||||
relationship.showingReblogs.flatMap { me.update(isShowingReblogs: $0, by: user) }
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import CommonOSLog
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
|
||||
private struct MastodonFollowContext {
|
||||
let sourceUserID: MastodonUser.ID
|
||||
let targetUserID: MastodonUser.ID
|
||||
@ -121,5 +121,56 @@ extension APIService {
|
||||
let response = try result.get()
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
public func toggleShowReblogs(
|
||||
for user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
else { throw APIError.implicit(.badRequest) }
|
||||
|
||||
let me = authentication.user
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
|
||||
let oldShowReblogs = me.showingReblogsBy.contains(user)
|
||||
let newShowReblogs = (oldShowReblogs == false)
|
||||
|
||||
do {
|
||||
let response = try await Mastodon.API.Account.follow(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: user.id,
|
||||
followQueryType: .follow(query: .init(reblogs: newShowReblogs)),
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
result = .success(response)
|
||||
} catch {
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
Persistence.MastodonUser.update(
|
||||
mastodonUser: user,
|
||||
context: Persistence.MastodonUser.RelationshipContext(
|
||||
entity: response.value,
|
||||
me: me,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
case .failure:
|
||||
// rollback
|
||||
user.update(isShowingReblogs: oldShowReblogs, by: me)
|
||||
}
|
||||
}
|
||||
|
||||
return try result.get()
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +198,8 @@ public enum L10n {
|
||||
public static let follow = L10n.tr("Localizable", "Common.Controls.Friendship.Follow")
|
||||
/// Following
|
||||
public static let following = L10n.tr("Localizable", "Common.Controls.Friendship.Following")
|
||||
/// Hide Reblogs
|
||||
public static let hideReblogs = L10n.tr("Localizable", "Common.Controls.Friendship.HideReblogs")
|
||||
/// Mute
|
||||
public static let mute = L10n.tr("Localizable", "Common.Controls.Friendship.Mute")
|
||||
/// Muted
|
||||
@ -210,6 +212,8 @@ public enum L10n {
|
||||
public static let pending = L10n.tr("Localizable", "Common.Controls.Friendship.Pending")
|
||||
/// Request
|
||||
public static let request = L10n.tr("Localizable", "Common.Controls.Friendship.Request")
|
||||
/// Show Reblogs
|
||||
public static let showReblogs = L10n.tr("Localizable", "Common.Controls.Friendship.ShowReblogs")
|
||||
/// Unblock
|
||||
public static let unblock = L10n.tr("Localizable", "Common.Controls.Friendship.Unblock")
|
||||
/// Unblock %@
|
||||
@ -709,6 +713,12 @@ public enum L10n {
|
||||
/// Block Account
|
||||
public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title")
|
||||
}
|
||||
public enum ConfirmHideReblogs {
|
||||
/// Confirm to hide reblogs
|
||||
public static let message = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message")
|
||||
/// Hide reblogs
|
||||
public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title")
|
||||
}
|
||||
public enum ConfirmMuteUser {
|
||||
/// Confirm to mute %@
|
||||
public static func message(_ p1: Any) -> String {
|
||||
@ -717,6 +727,12 @@ public enum L10n {
|
||||
/// Mute Account
|
||||
public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title")
|
||||
}
|
||||
public enum ConfirmShowReblogs {
|
||||
/// Confirm to show reblogs
|
||||
public static let message = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message")
|
||||
/// Show Reblogs
|
||||
public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title")
|
||||
}
|
||||
public enum ConfirmUnblockUser {
|
||||
/// Confirm to unblock %@
|
||||
public static func message(_ p1: Any) -> String {
|
||||
|
@ -77,6 +77,9 @@ Please check your internet connection.";
|
||||
"Common.Controls.Friendship.UnblockUser" = "Unblock %@";
|
||||
"Common.Controls.Friendship.Unmute" = "Unmute";
|
||||
"Common.Controls.Friendship.UnmuteUser" = "Unmute %@";
|
||||
"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs";
|
||||
"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs";
|
||||
|
||||
"Common.Controls.Keyboard.Common.ComposeNewPost" = "Compose New Post";
|
||||
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
|
||||
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
|
||||
@ -262,6 +265,10 @@ uploaded to Mastodon.";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Unblock Account";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Confirm to unmute %@";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Unmute Account";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs";
|
||||
"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide reblogs";
|
||||
"Scene.Profile.SegmentedControl.About" = "About";
|
||||
"Scene.Profile.SegmentedControl.Media" = "Media";
|
||||
"Scene.Profile.SegmentedControl.Posts" = "Posts";
|
||||
|
@ -45,11 +45,23 @@ extension MastodonMenu {
|
||||
case reportUser(ReportUserActionContext)
|
||||
case shareUser(ShareUserActionContext)
|
||||
case bookmarkStatus(BookmarkStatusActionContext)
|
||||
case hideReblogs(HideReblogsActionContext)
|
||||
case shareStatus
|
||||
case deleteStatus
|
||||
|
||||
func build(delegate: MastodonMenuDelegate) -> BuiltAction {
|
||||
switch self {
|
||||
case .hideReblogs(let context):
|
||||
let title = context.showReblogs ? L10n.Common.Controls.Friendship.hideReblogs : L10n.Common.Controls.Friendship.showReblogs
|
||||
let reblogAction = BuiltAction(
|
||||
title: title,
|
||||
image: UIImage(systemName: "arrow.2.squarepath")
|
||||
) { [weak delegate] in
|
||||
guard let delegate = delegate else { return }
|
||||
delegate.menuAction(self)
|
||||
}
|
||||
|
||||
return reblogAction
|
||||
case .muteUser(let context):
|
||||
let muteAction = BuiltAction(
|
||||
title: context.isMuting ? L10n.Common.Controls.Friendship.unmuteUser(context.name) : L10n.Common.Controls.Friendship.muteUser(context.name),
|
||||
@ -205,5 +217,12 @@ extension MastodonMenu {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct HideReblogsActionContext {
|
||||
public let showReblogs: Bool
|
||||
|
||||
public init(showReblogs: Bool) {
|
||||
self.showReblogs = showReblogs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import MastodonLocalization
|
||||
import CoreDataStack
|
||||
|
||||
public enum RelationshipAction: Int, CaseIterable {
|
||||
case showReblogs
|
||||
case isMyself
|
||||
case followingBy
|
||||
case blockingBy
|
||||
@ -27,7 +28,7 @@ public enum RelationshipAction: Int, CaseIterable {
|
||||
case edit
|
||||
case editing
|
||||
case updating
|
||||
|
||||
|
||||
public var option: RelationshipActionOptionSet {
|
||||
return RelationshipActionOptionSet(rawValue: 1 << rawValue)
|
||||
}
|
||||
@ -57,7 +58,7 @@ public struct RelationshipActionOptionSet: OptionSet {
|
||||
public static let edit = RelationshipAction.edit.option
|
||||
public static let editing = RelationshipAction.editing.option
|
||||
public static let updating = RelationshipAction.updating.option
|
||||
|
||||
public static let showReblogs = RelationshipAction.showReblogs.option
|
||||
public static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating]
|
||||
|
||||
public func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? {
|
||||
@ -75,24 +76,24 @@ public struct RelationshipActionOptionSet: OptionSet {
|
||||
return " "
|
||||
}
|
||||
switch highPriorityAction {
|
||||
case .isMyself: return ""
|
||||
case .followingBy: return " "
|
||||
case .blockingBy: return " "
|
||||
case .none: return " "
|
||||
case .follow: return L10n.Common.Controls.Friendship.follow
|
||||
case .request: return L10n.Common.Controls.Friendship.request
|
||||
case .pending: return L10n.Common.Controls.Friendship.pending
|
||||
case .following: return L10n.Common.Controls.Friendship.following
|
||||
case .muting: return L10n.Common.Controls.Friendship.muted
|
||||
case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user (deprecated)
|
||||
case .blocking: return L10n.Common.Controls.Friendship.blocked
|
||||
case .suspended: return L10n.Common.Controls.Friendship.follow
|
||||
case .edit: return L10n.Common.Controls.Friendship.editInfo
|
||||
case .editing: return L10n.Common.Controls.Actions.done
|
||||
case .updating: return " "
|
||||
case .isMyself: return ""
|
||||
case .followingBy: return " "
|
||||
case .blockingBy: return " "
|
||||
case .none: return " "
|
||||
case .follow: return L10n.Common.Controls.Friendship.follow
|
||||
case .request: return L10n.Common.Controls.Friendship.request
|
||||
case .pending: return L10n.Common.Controls.Friendship.pending
|
||||
case .following: return L10n.Common.Controls.Friendship.following
|
||||
case .muting: return L10n.Common.Controls.Friendship.muted
|
||||
case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user (deprecated)
|
||||
case .blocking: return L10n.Common.Controls.Friendship.blocked
|
||||
case .suspended: return L10n.Common.Controls.Friendship.follow
|
||||
case .edit: return L10n.Common.Controls.Friendship.editInfo
|
||||
case .editing: return L10n.Common.Controls.Actions.done
|
||||
case .updating: return " "
|
||||
case .showReblogs: return " "
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public final class RelationshipViewModel {
|
||||
@ -114,6 +115,7 @@ public final class RelationshipViewModel {
|
||||
@Published public var isFollowing = false
|
||||
@Published public var isFollowingBy = false
|
||||
@Published public var isMuting = false
|
||||
@Published public var showReblogs = false
|
||||
@Published public var isBlocking = false
|
||||
@Published public var isBlockingBy = false
|
||||
@Published public var isSuspended = false
|
||||
@ -184,6 +186,7 @@ extension RelationshipViewModel {
|
||||
self.isBlockingBy = optionSet.contains(.blockingBy)
|
||||
self.isBlocking = optionSet.contains(.blocking)
|
||||
self.isSuspended = optionSet.contains(.suspended)
|
||||
self.showReblogs = optionSet.contains(.showReblogs)
|
||||
|
||||
self.optionSet = optionSet
|
||||
}
|
||||
@ -196,6 +199,7 @@ extension RelationshipViewModel {
|
||||
isBlockingBy = false
|
||||
isBlocking = false
|
||||
optionSet = nil
|
||||
showReblogs = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,7 +218,8 @@ extension RelationshipViewModel {
|
||||
let isMuting = user.mutingBy.contains(me)
|
||||
let isBlockingBy = me.blockingBy.contains(user)
|
||||
let isBlocking = user.blockingBy.contains(me)
|
||||
|
||||
let isShowingReblogs = me.showingReblogsBy.contains(user)
|
||||
|
||||
var optionSet: RelationshipActionOptionSet = [.follow]
|
||||
|
||||
if isMyself {
|
||||
@ -252,7 +257,11 @@ extension RelationshipViewModel {
|
||||
if user.suspended {
|
||||
optionSet.insert(.suspended)
|
||||
}
|
||||
|
||||
|
||||
if isShowingReblogs {
|
||||
optionSet.insert(.showReblogs)
|
||||
}
|
||||
|
||||
return optionSet
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.7</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>147</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -15,8 +15,8 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.7</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>147</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.7</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>147</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
@ -17,23 +17,23 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.7</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>147</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>4</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
|
Loading…
x
Reference in New Issue
Block a user