Compare commits

...

4 Commits

16 changed files with 306 additions and 180 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
Dist.xcconfig
.DS_Store
MyPlayground.playground/

View File

@ -25,7 +25,6 @@
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; };
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; };
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; };
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */; };
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */; };
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */; };
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */; };
@ -95,6 +94,7 @@
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = D63CC701290EC0B8000E19DE /* Sentry */; };
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63D8DF32850FE7A008D95E1 /* ViewTags.swift */; };
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */; };
D6403CC224A6B72D00E81C55 /* VisualEffectImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */; };
@ -195,7 +195,6 @@
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */; };
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */; };
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = D69CCBBE249E6EFD000AF167 /* CrashReporter */; };
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */; };
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; };
@ -379,7 +378,6 @@
D60E2F252442372B005F8713 /* AccountMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMO.swift; sourceTree = "<group>"; };
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazilyDecoding.swift; sourceTree = "<group>"; };
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCachePersistentStore.swift; sourceTree = "<group>"; };
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusesViewController.swift; sourceTree = "<group>"; };
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinksViewController.swift; sourceTree = "<group>"; };
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkTableViewCell.swift; sourceTree = "<group>"; };
@ -448,6 +446,7 @@
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNavigationController.swift; sourceTree = "<group>"; };
D6370B9B24421FF30092A7FF /* Tusker.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Tusker.xcdatamodel; sourceTree = "<group>"; };
D63CC703290EC472000E19DE /* Dist.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Dist.xcconfig; sourceTree = "<group>"; };
D63D8DF32850FE7A008D95E1 /* ViewTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTags.swift; sourceTree = "<group>"; };
D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectImageButton.swift; sourceTree = "<group>"; };
@ -682,10 +681,10 @@
buildActionMask = 2147483647;
files = (
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */,
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
D6552367289870790048A653 /* ScreenCorners in Frameworks */,
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1343,6 +1342,7 @@
D6D4DDC3212518A000E1C4BB = {
isa = PBXGroup;
children = (
D63CC703290EC472000E19DE /* Dist.xcconfig */,
D674A50727F910F300BA03AC /* Pachyderm */,
D6D4DDCE212518A000E1C4BB /* Tusker */,
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
@ -1469,7 +1469,6 @@
D6F2E960249E772F005846BB /* Crash Reporter */ = {
isa = PBXGroup;
children = (
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */,
D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */,
D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */,
);
@ -1501,7 +1500,7 @@
D6F953E52125197500CF0F2B /* Embed Frameworks */,
D65F612C23AE957600F3CFD3 /* Embed debug-only frameworks */,
D6E3438F2659849800C4AA01 /* Embed Foundation Extensions */,
D6F1F9E127B0677000CB7D88 /* ShellScript */,
D63CC704290EC913000E19DE /* ShellScript */,
);
buildRules = (
);
@ -1510,11 +1509,11 @@
);
name = Tusker;
packageProductDependencies = (
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
D674A50827F9128D00BA03AC /* Pachyderm */,
D6552366289870790048A653 /* ScreenCorners */,
D63CC701290EC0B8000E19DE /* Sentry */,
);
productName = Tusker;
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
@ -1621,10 +1620,10 @@
);
mainGroup = D6D4DDC3212518A000E1C4BB;
packageReferences = (
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */,
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */,
D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
);
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
projectDirPath = "";
@ -1701,6 +1700,26 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
D63CC704290EC913000E19DE /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}",
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/bash;
shellScript = "echo \"$CONFIGURATION\"\nif [ \"$CONFIGURATION\" == \"Dist\" ]; then\nif which sentry-cli >/dev/null; then\nexport SENTRY_ORG=vaccor\nexport SENTRY_PROJECT=tusker\nERROR=$(sentry-cli upload-dif \"$DWARF_DSYM_FOLDER_PATH\" --force-foreground 2>&1 >/dev/null)\nif [ ! $? -eq 0 ]; then\necho \"sentry-cli uploaded debug info\"\nelse\necho \"error: sentry-cli - $ERROR\"\nfi\nelse\necho \"error: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases\"\nfi\nfi\n";
showEnvVarsInLog = 0;
};
D65F612C23AE957600F3CFD3 /* Embed debug-only frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -1723,24 +1742,6 @@
shellPath = /bin/sh;
shellScript = "#if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n# echo \"Embedding ${SCRIPT_INPUT_FILE_0}\"\n# cp -R $SCRIPT_INPUT_FILE_0 $SCRIPT_OUTPUT_FILE_0\n# codesign --force --verbose --sign $EXPANDED_CODE_SIGN_IDENTITY $SCRIPT_OUTPUT_FILE_0\n# \n# echo \"Embedding ${SCRIPT_INPUT_FILE_1}\"\n# cp -R $SCRIPT_INPUT_FILE_1 $SCRIPT_OUTPUT_FILE_1\n# codesign --force --verbose --sign $EXPANDED_CODE_SIGN_IDENTITY $SCRIPT_OUTPUT_FILE_1\n#else\n# echo \"Skipping embedding debug frameworks\"\n#fi\n";
};
D6F1F9E127B0677000CB7D88 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\n# the nested framework doens't get signed automatically for some reason\n# so we sign it ourselves\n#codesign --force --verbose --sign $EXPANDED_CODE_SIGN_IDENTITY \"$BUILT_PRODUCTS_DIR/Tusker.app/Frameworks/Pachyderm.framework/Frameworks/WebURL.framework\"\n\n# xcode wants to include the weburl framework twice for some reason, but the app store doesn't like nested frameworks\n# we already have a copy in the app's Frameworks/ dir, so we can delete the nested one\nif [ \"$(ls \"$BUILT_PRODUCTS_DIR/Tusker.app/Frameworks/Pachyderm.framework/Frameworks/\")\" -ne \"WebURL.framework\" ]; then\n exit 1\nfi\nrm -rf \"$BUILT_PRODUCTS_DIR/Tusker.app/Frameworks/Pachyderm.framework/Frameworks/\"\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -1991,7 +1992,6 @@
D677284E24ECC01D00C732D3 /* Draft.swift in Sources */,
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */,
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */,
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
@ -2087,8 +2087,164 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
D63CC705290ECE77000E19DE /* Dist */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D63CC703290EC472000E19DE /* Dist.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_OPTIMIZE_OBJECT_LIFETIME = YES;
VALIDATE_PRODUCT = YES;
};
name = Dist;
};
D63CC706290ECE77000E19DE /* Dist */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2022.1;
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
};
name = Dist;
};
D63CC707290ECE77000E19DE /* Dist */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = TuskerTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tusker.app/Tusker";
};
name = Dist;
};
D63CC708290ECE77000E19DE /* Dist */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = TuskerUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Tusker;
};
name = Dist;
};
D63CC709290ECE77000E19DE /* Dist */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2022.1;
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.OpenInTusker;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
};
name = Dist;
};
D6D4DDF2212518A200E1C4BB /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D63CC703290EC472000E19DE /* Dist.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
@ -2411,6 +2567,7 @@
buildConfigurations = (
D6D4DDF2212518A200E1C4BB /* Debug */,
D6D4DDF3212518A200E1C4BB /* Release */,
D63CC705290ECE77000E19DE /* Dist */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@ -2420,6 +2577,7 @@
buildConfigurations = (
D6D4DDF5212518A200E1C4BB /* Debug */,
D6D4DDF6212518A200E1C4BB /* Release */,
D63CC706290ECE77000E19DE /* Dist */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@ -2429,6 +2587,7 @@
buildConfigurations = (
D6D4DDF8212518A200E1C4BB /* Debug */,
D6D4DDF9212518A200E1C4BB /* Release */,
D63CC707290ECE77000E19DE /* Dist */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@ -2438,6 +2597,7 @@
buildConfigurations = (
D6D4DDFB212518A200E1C4BB /* Debug */,
D6D4DDFC212518A200E1C4BB /* Release */,
D63CC708290ECE77000E19DE /* Dist */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@ -2447,6 +2607,7 @@
buildConfigurations = (
D6E343B7265AAD6B00C4AA01 /* Debug */,
D6E343B8265AAD6B00C4AA01 /* Release */,
D63CC709290ECE77000E19DE /* Dist */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@ -2462,6 +2623,14 @@
minimumVersion = 2.3.2;
};
};
D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 7.29.0;
};
};
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kylebshr/ScreenCorners";
@ -2478,14 +2647,6 @@
kind = branch;
};
};
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/microsoft/plcrashreporter";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 1.11.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -2498,6 +2659,11 @@
isa = XCSwiftPackageProductDependency;
productName = Pachyderm;
};
D63CC701290EC0B8000E19DE /* Sentry */ = {
isa = XCSwiftPackageProductDependency;
package = D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
productName = Sentry;
};
D6552366289870790048A653 /* ScreenCorners */ = {
isa = XCSwiftPackageProductDependency;
package = D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */;
@ -2512,11 +2678,6 @@
isa = XCSwiftPackageProductDependency;
productName = Pachyderm;
};
D69CCBBE249E6EFD000AF167 /* CrashReporter */ = {
isa = XCSwiftPackageProductDependency;
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
productName = CrashReporter;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */

View File

@ -127,7 +127,7 @@
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
buildConfiguration = "Dist"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -7,22 +7,19 @@
//
import UIKit
import CrashReporter
import CoreData
import OSLog
import Sentry
let stateRestorationLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "StateRestoration")
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
static private(set) var crashReporter: PLCrashReporter!
static var pendingCrashReport: PLCrashReport?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
#if !DEBUG
setupCrashReporter()
#endif
configureSentry()
AppShortcutItem.createItems(for: application)
@ -52,22 +49,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}
private func setupCrashReporter() {
let config = PLCrashReporterConfig(signalHandlerType: .BSD, symbolicationStrategy: .all)
AppDelegate.crashReporter = PLCrashReporter(configuration: config)
let callbacksPtr = UnsafeMutablePointer<PLCrashReporterCallbacks>.allocate(capacity: 1)
callbacksPtr.pointee = PLCrashReporterCallbacks(version: 0, context: nil, handleSignal: crashCallback)
// setCrashCallbacks, it's poorly imported into Swift
AppDelegate.crashReporter.setCrash(callbacksPtr)
if AppDelegate.crashReporter.hasPendingCrashReport(),
let data = try? AppDelegate.crashReporter.loadPendingCrashReportDataAndReturnError(),
let report = try? PLCrashReport(data: data) {
AppDelegate.crashReporter.purgePendingCrashReport()
AppDelegate.pendingCrashReport = report
private func configureSentry() {
guard let dsn = Bundle.main.object(forInfoDictionaryKey: "SentryDSN") as? String,
!dsn.isEmpty else {
return
}
SentrySDK.start { options in
#if DEBUG
options.debug = true
options.environment = "dev"
#endif
AppDelegate.crashReporter.enable()
// the '//' in the full url can't be escaped, so we have to add the scheme back
options.dsn = "https://\(dsn)"
options.enableSwizzling = false
options.enableAutoSessionTracking = false
options.enableOutOfMemoryTracking = false
options.enableAutoPerformanceTracking = false
options.enableNetworkTracking = false
options.enableAppHangTracking = false
options.enableCoreDataTracking = false
options.beforeSend = { event in
Preferences.shared.reportErrorsAutomatically ? event : nil
}
}
}
override func buildMenu(with builder: UIMenuBuilder) {
@ -119,11 +126,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}
private func crashCallback(info: UnsafeMutablePointer<siginfo_t>!, uap: UnsafeMutablePointer<ucontext_t>!, context: UnsafeMutableRawPointer?) {
// read logs on the main queue in order to block further user interaction while we're crashing
// since getting the log data can take upwards of a second, even for very few entries
// synchronously dispatch because the callback can't return until we're ready to abort
DispatchQueue.main.sync {
Logging.writeDataForCrash()
}
}

View File

@ -179,20 +179,21 @@ class MastodonCachePersistentStore: NSPersistentContainer {
}
@discardableResult
private func upsert(relationship: Relationship) -> RelationshipMO {
if let relationshipMO = self.relationship(forAccount: relationship.id, in: self.backgroundContext) {
private func upsert(relationship: Relationship, in context: NSManagedObjectContext) -> RelationshipMO {
if let relationshipMO = self.relationship(forAccount: relationship.id, in: context) {
relationshipMO.updateFrom(apiRelationship: relationship, container: self)
return relationshipMO
} else {
let relationshipMO = RelationshipMO(apiRelationship: relationship, container: self, context: self.backgroundContext)
let relationshipMO = RelationshipMO(apiRelationship: relationship, container: self, context: context)
return relationshipMO
}
}
func addOrUpdate(relationship: Relationship, completion: ((RelationshipMO) -> Void)? = nil) {
backgroundContext.perform {
let relationshipMO = self.upsert(relationship: relationship)
self.save(context: self.backgroundContext)
func addOrUpdate(relationship: Relationship, in context: NSManagedObjectContext? = nil, completion: ((RelationshipMO) -> Void)? = nil) {
let context = context ?? backgroundContext
context.perform {
let relationshipMO = self.upsert(relationship: relationship, in: context)
self.save(context: context)
completion?(relationshipMO)
self.relationshipSubject.send(relationship.id)
}

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SentryDSN</key>
<string>$(SENTRY_DSN)</string>
<key>OSLogPreferences</key>
<dict>
<key>space.vaccor.Tusker</key>

View File

@ -8,7 +8,6 @@
import UIKit
import Pachyderm
import CrashReporter
import MessageUI
import CoreData
@ -32,15 +31,10 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
window = UIWindow(windowScene: windowScene)
if let report = AppDelegate.pendingCrashReport {
AppDelegate.pendingCrashReport = nil
handlePendingCrashReport(report, session: session)
} else {
showAppOrOnboardingUI(session: session)
if connectionOptions.urlContexts.count > 0 {
self.scene(scene, openURLContexts: connectionOptions.urlContexts)
}
}
window!.makeKeyAndVisible()
@ -142,19 +136,6 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
}
}
private func handlePendingCrashReport(_ report: PLCrashReport, session: UISceneSession) {
#if !DEBUG
guard MFMailComposeViewController.canSendMail() else {
print("Cannot send email")
showAppOrOnboardingUI(session: session)
return
}
window!.rootViewController = CrashReporterViewController.create(report: report, dismiss: {
self.showAppOrOnboardingUI()
})
#endif
}
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
let session = session ?? window!.windowScene!.session

View File

@ -149,6 +149,7 @@ class Preferences: Codable, ObservableObject {
// MARK: Advanced
@Published var statusContentType: StatusContentType = .plain
@Published var reportErrorsAutomatically = true
// MARK:
@Published var hasShownLocalTimelineDescription = false

View File

@ -137,8 +137,7 @@ struct ComposeView: View {
}
MainComposeTextView(
draft: draft,
placeholder: Text("What's on your mind?")
draft: draft
)
if let poll = draft.poll {

View File

@ -11,7 +11,15 @@ import Pachyderm
struct MainComposeTextView: View {
@ObservedObject var draft: Draft
let placeholder: Text
let placeholder: Text = {
let components = Calendar.current.dateComponents([.month, .day], from: Date())
if components.month == 9 && components.day == 21 {
return Text("Do you remember?")
} else if components.month == 10 && components.day == 31 {
return Text("Post something spooky!")
}
return Text("What's on your mind?")
}()
let minHeight: CGFloat = 150
@State private var height: CGFloat?

View File

@ -1,64 +0,0 @@
//
// CrashReporterViewController.swift
// Tusker
//
// Created by Shadowfacts on 3/29/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import CrashReporter
class CrashReporterViewController: IssueReporterViewController {
private let report: PLCrashReport
private var logURL: URL?
override var preamble: String {
"Tusker has detected that it crashed the last time it was running. You can email the report to the developer or skip sending and continue to the app. You may review the report below before sending.\n\nIf you choose to send the report, please include any additional details about what you were doing prior to the crash that may be pertinent."
}
override var subject: String {
"Tusker Crash Report"
}
static func create(report: PLCrashReport, dismiss: @escaping () -> Void) -> UINavigationController {
return create(CrashReporterViewController(report: report, dismiss: dismiss))
}
private init(report: PLCrashReport, dismiss: @escaping () -> Void) {
self.report = report
self.logURL = Logging.logURLForLastCrash()
let reportText = PLCrashReportTextFormatter.stringValue(for: report, with: PLCrashReportTextFormatiOS)!
let timestamp = ISO8601DateFormatter().string(from: report.systemInfo.timestamp)
let reportFilename = "Tusker-crash-\(timestamp).crash"
super.init(reportText: reportText, reportFilename: reportFilename, dismiss: dismiss)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = NSLocalizedString("Crash Detected", comment: "crash reporter title")
}
override func getLogData() async -> (Data, String)? {
guard let logURL,
let data = try? Data(contentsOf: logURL) else {
return nil
}
return (data, logURL.lastPathComponent)
}
override func finishedReport() {
super.finishedReport()
if let logURL {
try? FileManager.default.removeItem(at: logURL)
}
}
}

View File

@ -7,7 +7,6 @@
//
import UIKit
import CrashReporter
import MessageUI
import OSLog

View File

@ -15,6 +15,7 @@ struct AdvancedPrefsView : View {
var body: some View {
List {
formattingSection
errorReportingSection
cachingSection
}
.listStyle(InsetGroupedListStyle())
@ -36,6 +37,22 @@ struct AdvancedPrefsView : View {
}
}
var errorReportingSection: some View {
Section {
Toggle("Report Errors Automatically", isOn: $preferences.reportErrorsAutomatically)
} footer: {
var privacyPolicy: AttributedString = "Privacy Policy"
let _ = privacyPolicy.link = URL(string: "https://vaccor.space/tusker#privacy")!
if preferences.reportErrorsAutomatically {
Text(AttributedString("App crashes and errors will be automatically reported to the developer. You may be prompted to add additional information.\n") + privacyPolicy)
.lineLimit(nil)
} else {
Text(AttributedString("Errors will not be reported automatically. When a crash occurs, you may be asked to report it manually.\n") + privacyPolicy)
.lineLimit(nil)
}
}
}
var cachingSection: some View {
Section(header: Text("Caching"), footer: Text("Clearing caches will restart the app.")) {
Button(action: clearCache) {

View File

@ -60,12 +60,23 @@ extension MenuActionProvider {
draft.visibility = .direct
self.navigationDelegate?.compose(editing: draft)
}),
UIDeferredMenuElement.uncached({ (elementHandler) in
Task { @MainActor in
if let action = await self.followAction(for: accountID, mastodonController: mastodonController) {
elementHandler([action])
} else {
elementHandler([])
UIDeferredMenuElement.uncached({ @MainActor elementHandler in
let relationship = Task {
await self.fetchRelationship(accountID: accountID, mastodonController: mastodonController)
}
// workaround for #198, may result in showing outdated relationship, so only do so where necessary
if ProcessInfo.processInfo.isiOSAppOnMac,
let mo = mastodonController.persistentContainer.relationship(forAccount: accountID),
let action = self.followAction(for: mo, mastodonController: mastodonController) {
elementHandler([action])
} else {
Task { @MainActor in
if let relationship = await relationship.value,
let action = self.followAction(for: relationship, mastodonController: mastodonController) {
elementHandler([action])
} else {
elementHandler([])
}
}
}
})
@ -381,16 +392,26 @@ extension MenuActionProvider {
})
}
private func followAction(for accountID: String, mastodonController: MastodonController) async -> UIMenuElement? {
guard let ownAccount = try? await mastodonController.getOwnAccount(),
accountID != ownAccount.id else {
private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? {
let req = Client.getRelationships(accounts: [accountID])
guard let (relationships, _) = try? await mastodonController.run(req),
let r = relationships.first else {
return nil
}
let request = Client.getRelationships(accounts: [accountID])
guard let (relationships, _) = try? await mastodonController.run(request),
let relationship = relationships.first else {
return await withCheckedContinuation { continuation in
mastodonController.persistentContainer.addOrUpdate(relationship: r, in: mastodonController.persistentContainer.viewContext) { mo in
continuation.resume(returning: mo)
}
}
}
@MainActor
private func followAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement? {
guard let ownAccount = mastodonController.account,
relationship.accountID != ownAccount.id else {
return nil
}
let accountID = relationship.accountID
let following = relationship.following
return createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus") { _ in
let request = (following ? Account.unfollow : Account.follow)(accountID)

View File

@ -40,7 +40,7 @@ actor TimelineLikeController<Item> {
willSet {
guard state.canTransition(to: newValue) else {
logger.error("State \(self.state.debugDescription, privacy: .public) cannot transition to \(newValue.debugDescription, privacy: .public)")
preconditionFailure("cannot transition to state")
fatalError("State \(state) cannot transition to \(newValue)")
}
logger.debug("State: \(self.state.debugDescription, privacy: .public) -> \(newValue.debugDescription, privacy: .public)")
}
@ -123,7 +123,7 @@ actor TimelineLikeController<Item> {
private func emit(event: Event) async {
guard state.canEmit(event: event) else {
logger.error("State \(self.state.debugDescription, privacy: .public) cannot emit event: \(event.debugDescription, privacy: .public)")
preconditionFailure("state cannot emit event")
fatalError("State \(state) cannot emit event: \(event)")
}
switch event {
case .addLoadingIndicator:

View File

@ -104,7 +104,7 @@ class AttachmentView: GIFImageView {
func loadAttachment() {
guard AttachmentsContainerView.supportedAttachmentTypes.contains(attachment.kind) else {
preconditionFailure("invalid attachment type")
fatalError("invalid attachment type")
}
if let hash = attachment.blurHash {
@ -137,7 +137,7 @@ class AttachmentView: GIFImageView {
case .gifv:
loadGifv()
default:
preconditionFailure("invalid attachment type")
fatalError("invalid attachment type")
}
}