mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-05-17 22:16:10 +08:00
feat: home screen widget for iOS. (#494)
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 70;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -22,6 +22,11 @@
|
||||
E530B1AD283B54DA004E8EB6 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E530B1AC283B54DA004E8EB6 /* ActionViewController.swift */; };
|
||||
E530B1B0283B54DA004E8EB6 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E530B1AE283B54DA004E8EB6 /* MainInterface.storyboard */; };
|
||||
E530B1B4283B54DA004E8EB6 /* Action Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E530B1A6283B54DA004E8EB6 /* Action Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
E573DDF82D3E273F00831A51 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E573DDF72D3E273F00831A51 /* WidgetKit.framework */; };
|
||||
E573DDFA2D3E273F00831A51 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E573DDF92D3E273F00831A51 /* SwiftUI.framework */; };
|
||||
E573DE072D3E274000831A51 /* Story Widget Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E573DDF62D3E273F00831A51 /* Story Widget Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
E573DE392D3E282700831A51 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = E573DE382D3E282700831A51 /* Alamofire */; };
|
||||
E573DE3E2D3E28CD00831A51 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = E573DE3D2D3E28CD00831A51 /* SwiftSoup */; };
|
||||
E575B6F127EBC6DB002B1508 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E575B6F027EBC6DA002B1508 /* CloudKit.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -40,12 +45,19 @@
|
||||
remoteGlobalIDString = E530B1A5283B54DA004E8EB6;
|
||||
remoteInfo = "Action Extension";
|
||||
};
|
||||
E573DE052D3E274000831A51 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = E573DDF52D3E273F00831A51;
|
||||
remoteInfo = StoryWidgetExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 12;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
@ -60,6 +72,7 @@
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
E51D52B7283B464E00FC8DD8 /* Share Extension.appex in Embed App Extensions */,
|
||||
E573DE072D3E274000831A51 /* Story Widget Extension.appex in Embed App Extensions */,
|
||||
E530B1B4283B54DA004E8EB6 /* Action Extension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
@ -96,11 +109,28 @@
|
||||
E530B1AF283B54DA004E8EB6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
E530B1B1283B54DA004E8EB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E530B1B9283B54E4004E8EB6 /* Action Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Action Extension.entitlements"; sourceTree = "<group>"; };
|
||||
E573DDF62D3E273F00831A51 /* Story Widget Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Story Widget Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E573DDF72D3E273F00831A51 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
E573DDF92D3E273F00831A51 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
E575B6EF27EBC6C6002B1508 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
E575B6F027EBC6DA002B1508 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
|
||||
E59F28EE283B477D00512089 /* Share Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share Extension.entitlements"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
E573DE0B2D3E274000831A51 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = E573DDF52D3E273F00831A51 /* Story Widget Extension */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
E573DDFB2D3E273F00831A51 /* StoryWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (E573DE0B2D3E274000831A51 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = StoryWidget; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
@ -126,6 +156,17 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
E573DDF32D3E273F00831A51 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E573DE392D3E282700831A51 /* Alamofire in Frameworks */,
|
||||
E573DDFA2D3E273F00831A51 /* SwiftUI.framework in Frameworks */,
|
||||
E573DDF82D3E273F00831A51 /* WidgetKit.framework in Frameworks */,
|
||||
E573DE3E2D3E28CD00831A51 /* SwiftSoup in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@ -147,6 +188,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
E51D52AE283B464E00FC8DD8 /* Share Extension */,
|
||||
E530B1A9283B54DA004E8EB6 /* Action Extension */,
|
||||
E573DDFB2D3E273F00831A51 /* StoryWidget */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
D79CD63C88FF49EF451AFDDF /* Pods */,
|
||||
B3F4F49CF582C662A01499C0 /* Frameworks */,
|
||||
@ -159,6 +201,7 @@
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
E51D52AD283B464E00FC8DD8 /* Share Extension.appex */,
|
||||
E530B1A6283B54DA004E8EB6 /* Action Extension.appex */,
|
||||
E573DDF62D3E273F00831A51 /* Story Widget Extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -185,6 +228,8 @@
|
||||
E575B6F027EBC6DA002B1508 /* CloudKit.framework */,
|
||||
E530B1A7283B54DA004E8EB6 /* UniformTypeIdentifiers.framework */,
|
||||
8BF0A917F40A838BF30D8F4C /* Pods_Runner.framework */,
|
||||
E573DDF72D3E273F00831A51 /* WidgetKit.framework */,
|
||||
E573DDF92D3E273F00831A51 /* SwiftUI.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@ -244,6 +289,7 @@
|
||||
dependencies = (
|
||||
E51D52B6283B464E00FC8DD8 /* PBXTargetDependency */,
|
||||
E530B1B3283B54DA004E8EB6 /* PBXTargetDependency */,
|
||||
E573DE062D3E274000831A51 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
@ -284,13 +330,37 @@
|
||||
productReference = E530B1A6283B54DA004E8EB6 /* Action Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
E573DDF52D3E273F00831A51 /* Story Widget Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = E573DE0C2D3E274000831A51 /* Build configuration list for PBXNativeTarget "Story Widget Extension" */;
|
||||
buildPhases = (
|
||||
E573DDF22D3E273F00831A51 /* Sources */,
|
||||
E573DDF32D3E273F00831A51 /* Frameworks */,
|
||||
E573DDF42D3E273F00831A51 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
E573DDFB2D3E273F00831A51 /* StoryWidget */,
|
||||
);
|
||||
name = "Story Widget Extension";
|
||||
packageProductDependencies = (
|
||||
E573DE382D3E282700831A51 /* Alamofire */,
|
||||
E573DE3D2D3E28CD00831A51 /* SwiftSoup */,
|
||||
);
|
||||
productName = StoryWidgetExtension;
|
||||
productReference = E573DDF62D3E273F00831A51 /* Story Widget Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1330;
|
||||
LastSwiftUpdateCheck = 1610;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
@ -304,6 +374,9 @@
|
||||
E530B1A5283B54DA004E8EB6 = {
|
||||
CreatedOnToolsVersion = 13.3;
|
||||
};
|
||||
E573DDF52D3E273F00831A51 = {
|
||||
CreatedOnToolsVersion = 16.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
@ -315,6 +388,10 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
packageReferences = (
|
||||
E573DE372D3E282700831A51 /* XCRemoteSwiftPackageReference "Alamofire" */,
|
||||
E573DE3C2D3E28CD00831A51 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
);
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@ -322,6 +399,7 @@
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
E51D52AC283B464E00FC8DD8 /* Share Extension */,
|
||||
E530B1A5283B54DA004E8EB6 /* Action Extension */,
|
||||
E573DDF52D3E273F00831A51 /* Story Widget Extension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -355,6 +433,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
E573DDF42D3E273F00831A51 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@ -372,7 +457,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -456,6 +541,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
E573DDF22D3E273F00831A51 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
@ -469,6 +561,11 @@
|
||||
target = E530B1A5283B54DA004E8EB6 /* Action Extension */;
|
||||
targetProxy = E530B1B2283B54DA004E8EB6 /* PBXContainerItemProxy */;
|
||||
};
|
||||
E573DE062D3E274000831A51 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = E573DDF52D3E273F00831A51 /* Story Widget Extension */;
|
||||
targetProxy = E573DE052D3E274000831A51 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
@ -575,7 +672,7 @@
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Hacki;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.news";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -718,7 +815,7 @@
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Hacki;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.news";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -753,7 +850,7 @@
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Hacki;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.news";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -789,7 +886,7 @@
|
||||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Share Extension";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -831,7 +928,7 @@
|
||||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Share Extension";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -869,7 +966,7 @@
|
||||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Share Extension";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -908,7 +1005,7 @@
|
||||
INFOPLIST_FILE = "Action Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Hacki";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -952,7 +1049,7 @@
|
||||
INFOPLIST_FILE = "Action Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Hacki";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -992,7 +1089,7 @@
|
||||
INFOPLIST_FILE = "Action Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Hacki";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -1010,6 +1107,136 @@
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
E573DE082D3E274000831A51 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = StoryWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = StoryWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Widget-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E573DE092D3E274000831A51 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = StoryWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = StoryWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Widget-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.jiaqi.hacki.Widget-Extension";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
E573DE0A2D3E274000831A51 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = StoryWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = StoryWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Widget-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@ -1053,7 +1280,49 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
E573DE0C2D3E274000831A51 /* Build configuration list for PBXNativeTarget "Story Widget Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E573DE082D3E274000831A51 /* Debug */,
|
||||
E573DE092D3E274000831A51 /* Release */,
|
||||
E573DE0A2D3E274000831A51 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
E573DE372D3E282700831A51 /* XCRemoteSwiftPackageReference "Alamofire" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Alamofire/Alamofire.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 5.10.2;
|
||||
};
|
||||
};
|
||||
E573DE3C2D3E28CD00831A51 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.7.6;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
E573DE382D3E282700831A51 /* Alamofire */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E573DE372D3E282700831A51 /* XCRemoteSwiftPackageReference "Alamofire" */;
|
||||
productName = Alamofire;
|
||||
};
|
||||
E573DE3D2D3E28CD00831A51 /* SwiftSoup */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E573DE3C2D3E28CD00831A51 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||
productName = SwiftSoup;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
|
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E530B1A5283B54DA004E8EB6"
|
||||
BuildableName = "Action Extension.appex"
|
||||
BlueprintName = "Action Extension"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E51D52AC283B464E00FC8DD8"
|
||||
BuildableName = "Share Extension.appex"
|
||||
BlueprintName = "Share Extension"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E573DDF52D3E273F00831A51"
|
||||
BuildableName = "Story Widget Extension.appex"
|
||||
BlueprintName = "Story Widget Extension"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.springboard">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E573DDF52D3E273F00831A51"
|
||||
BuildableName = "Story Widget Extension.appex"
|
||||
BlueprintName = "Story Widget Extension"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetKind"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetDefaultView"
|
||||
value = "timeline"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetFamily"
|
||||
value = "systemMedium"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
24
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
Normal file
24
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"originHash" : "1002c245c0fdae6ca9c33705b8fc0eaeec1eff55818735c136a20ed23937d94f",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "alamofire",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Alamofire/Alamofire.git",
|
||||
"state" : {
|
||||
"revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5",
|
||||
"version" : "5.10.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftsoup",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||
"state" : {
|
||||
"revision" : "0837db354faf9c9deb710dc597046edaadf5360f",
|
||||
"version" : "2.7.6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
ios/StoryWidget/Assets.xcassets/Contents.json
Normal file
6
ios/StoryWidget/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
36
ios/StoryWidget/Extensions/ArrayExtension.swift
Normal file
36
ios/StoryWidget/Extensions/ArrayExtension.swift
Normal file
@ -0,0 +1,36 @@
|
||||
import Foundation
|
||||
|
||||
extension Optional where Wrapped: Collection {
|
||||
var isMoreThanOne: Bool {
|
||||
guard let unwrapped = self else {
|
||||
return false
|
||||
}
|
||||
|
||||
if unwrapped.count > 1 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var isNullOrEmpty: Bool {
|
||||
guard let unwrapped = self else {
|
||||
return true
|
||||
}
|
||||
|
||||
return unwrapped.isEmpty
|
||||
}
|
||||
|
||||
var isNotNullOrEmpty: Bool {
|
||||
return !isNullOrEmpty
|
||||
}
|
||||
|
||||
var countOrZero: Int {
|
||||
guard let unwrapped = self else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return unwrapped.count
|
||||
}
|
||||
}
|
9
ios/StoryWidget/Extensions/Date+TimeAgoString.swift
Normal file
9
ios/StoryWidget/Extensions/Date+TimeAgoString.swift
Normal file
@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
extension Date {
|
||||
var timeAgoString: String {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
return formatter.localizedString(for: self, relativeTo: Date())
|
||||
}
|
||||
}
|
10
ios/StoryWidget/Extensions/Int+OrZero.swift
Normal file
10
ios/StoryWidget/Extensions/Int+OrZero.swift
Normal file
@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
extension Int? {
|
||||
var orZero: Int {
|
||||
guard let unwrapped = self else {
|
||||
return 0
|
||||
}
|
||||
return unwrapped
|
||||
}
|
||||
}
|
88
ios/StoryWidget/Extensions/StringExtension.swift
Normal file
88
ios/StoryWidget/Extensions/StringExtension.swift
Normal file
@ -0,0 +1,88 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSoup
|
||||
|
||||
public extension String {
|
||||
var isNotEmpty: Bool {
|
||||
!isEmpty
|
||||
}
|
||||
|
||||
var htmlStripped: String {
|
||||
do {
|
||||
let pRegex = try Regex("<p>")
|
||||
let iRegex = try Regex(#"\<i\>(.*?)\<\/i\>"#)
|
||||
let codeRegex = try Regex(#"\<pre\>\<code\>(.*?)\<\/code\>\<\/pre\>"#)
|
||||
.dotMatchesNewlines(true)
|
||||
let linkRegex = try Regex(#"\<a href=\"(.*?)\".*?\>.*?\<\/a\>"#)
|
||||
let res = try Entities.unescape(self)
|
||||
.replacing(pRegex, with: { match in
|
||||
"\n"
|
||||
})
|
||||
.replacing(iRegex, with: { match in
|
||||
if let m = match[1].substring {
|
||||
let matchedStr = String(m)
|
||||
return "**\(matchedStr)**"
|
||||
}
|
||||
return String()
|
||||
})
|
||||
.replacing(linkRegex, with: { match in
|
||||
if let m = match[1].substring {
|
||||
let matchedStr = String(m)
|
||||
return matchedStr
|
||||
}
|
||||
return String()
|
||||
})
|
||||
.withExtraLineBreak
|
||||
.replacing(codeRegex, with: { match in
|
||||
if let m = match[1].substring {
|
||||
let matchedStr = String(m)
|
||||
return "```" + String(matchedStr.replacing("\n\n", with: "``` \n ``` \n").dropLast(1)) + "```"
|
||||
}
|
||||
return String()
|
||||
})
|
||||
return res
|
||||
} catch {
|
||||
return error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
func toJSON() -> Any? {
|
||||
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
|
||||
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
|
||||
}
|
||||
|
||||
private var withExtraLineBreak: String {
|
||||
if isEmpty { return self }
|
||||
let range = startIndex..<index(endIndex, offsetBy: -1)
|
||||
var str = String(replacingOccurrences(of: "\n", with: "\n\n", range: range))
|
||||
while str.last?.isWhitespace == true {
|
||||
str = String(str.dropLast())
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional where Wrapped == String {
|
||||
var orEmpty: String {
|
||||
guard let unwrapped = self else {
|
||||
return ""
|
||||
}
|
||||
return unwrapped
|
||||
}
|
||||
|
||||
var htmlStripped: String{
|
||||
guard let unwrapped = self else {
|
||||
return ""
|
||||
}
|
||||
|
||||
return unwrapped.htmlStripped
|
||||
}
|
||||
|
||||
var isNotNullOrEmpty: Bool {
|
||||
guard let unwrapped = self else {
|
||||
return false
|
||||
}
|
||||
|
||||
return unwrapped.isNotEmpty
|
||||
}
|
||||
}
|
11
ios/StoryWidget/Info.plist
Normal file
11
ios/StoryWidget/Info.plist
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
54
ios/StoryWidget/Models/Comment.swift
Normal file
54
ios/StoryWidget/Models/Comment.swift
Normal file
@ -0,0 +1,54 @@
|
||||
public struct Comment: Item {
|
||||
public let id: Int
|
||||
public let parent: Int?
|
||||
public let text: String?
|
||||
public let type: String?
|
||||
public let by: String?
|
||||
public let time: Int
|
||||
public let kids: [Int]?
|
||||
public let level: Int?
|
||||
public var metadata: String {
|
||||
if let count = kids?.count, count != 0 {
|
||||
return "\(count) cmt\(count > 1 ? "s":"") | \(timeAgo) by \(by.orEmpty)"
|
||||
} else {
|
||||
return "\(timeAgo) by \(by.orEmpty)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Values below will always be nil for `Comment`.
|
||||
public let title: String?
|
||||
public let url: String?
|
||||
public let descendants: Int?
|
||||
public let score: Int?
|
||||
|
||||
|
||||
init(id: Int, parent: Int?, text: String?, by: String?, time: Int, kids: [Int]? = [Int](), level: Int? = 0) {
|
||||
self.id = id
|
||||
self.parent = parent
|
||||
self.text = text
|
||||
self.by = by
|
||||
self.time = time
|
||||
self.kids = kids
|
||||
self.level = level
|
||||
self.type = "comment"
|
||||
self.title = nil
|
||||
self.url = nil
|
||||
self.descendants = nil
|
||||
self.score = nil
|
||||
}
|
||||
|
||||
// Empty initializer
|
||||
init() {
|
||||
self.init(id: 0, parent: 0, text: "", by: "", time: 0)
|
||||
}
|
||||
|
||||
public func copyWith(text: String? = nil, level: Int? = nil) -> Comment {
|
||||
Comment(id: id,
|
||||
parent: parent,
|
||||
text: text ?? self.text,
|
||||
by: by,
|
||||
time: time,
|
||||
kids: kids ?? [Int](),
|
||||
level: level ?? self.level)
|
||||
}
|
||||
}
|
59
ios/StoryWidget/Models/Item.swift
Normal file
59
ios/StoryWidget/Models/Item.swift
Normal file
@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
|
||||
public protocol Item: Codable, Identifiable, Hashable {
|
||||
var id: Int { get }
|
||||
var parent: Int? { get }
|
||||
var title: String? { get }
|
||||
var text: String? { get }
|
||||
var url: String? { get }
|
||||
var type: String? { get }
|
||||
var by: String? { get }
|
||||
var score: Int? { get }
|
||||
var descendants: Int? { get }
|
||||
var time: Int { get }
|
||||
var kids: [Int]? { get }
|
||||
var metadata: String { get }
|
||||
}
|
||||
|
||||
public extension Item {
|
||||
var createdAtDate: Date {
|
||||
let date = Date(timeIntervalSince1970: Double(time))
|
||||
return date
|
||||
}
|
||||
|
||||
var createdAt: String {
|
||||
let date = Date(timeIntervalSince1970: Double(time))
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "MMM d, yyyy"
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
var timeAgo: String {
|
||||
let date = Date(timeIntervalSince1970: Double(time))
|
||||
return date.timeAgoString
|
||||
}
|
||||
|
||||
var formattedTime: String {
|
||||
Date(timeIntervalSince1970: Double(time)).formatted()
|
||||
}
|
||||
|
||||
var itemUrl: String {
|
||||
"https://news.ycombinator.com/item?id=\(self.id)"
|
||||
}
|
||||
|
||||
var readableUrl: String? {
|
||||
if let url = self.url {
|
||||
let domain = URL(string: url)?.host
|
||||
return domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var isJob: Bool {
|
||||
return type == "job"
|
||||
}
|
||||
|
||||
var isJobWithUrl: Bool {
|
||||
return type == "job" && text.isNullOrEmpty && url.isNotNullOrEmpty
|
||||
}
|
||||
}
|
48
ios/StoryWidget/Models/SearchFilter.swift
Normal file
48
ios/StoryWidget/Models/SearchFilter.swift
Normal file
@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
|
||||
public enum SearchFilter: Equatable, Hashable {
|
||||
case story
|
||||
case comment
|
||||
case dateRange(Date, Date)
|
||||
|
||||
var query: String {
|
||||
switch(self){
|
||||
case .story:
|
||||
return "story"
|
||||
case .comment:
|
||||
return "comment"
|
||||
case .dateRange(let startDate, let endDate):
|
||||
let startTimestamp = Int(startDate.timeIntervalSince1970.rounded())
|
||||
let endTimestamp = Int(endDate.timeIntervalSince1970.rounded())
|
||||
|
||||
if startTimestamp != endTimestamp {
|
||||
return "created_at_i>=\(startTimestamp),created_at_i<=\(endTimestamp)"
|
||||
} else {
|
||||
let updatedStartDate = Calendar.current.date(
|
||||
byAdding: .hour,
|
||||
value: -24,
|
||||
to: startDate)
|
||||
let updatedStartTimestamp = updatedStartDate?.timeIntervalSince1970
|
||||
|
||||
if let updatedStartTimestamp = updatedStartTimestamp?.rounded() {
|
||||
return "created_at_i>=\(Int(updatedStartTimestamp)),created_at_i<=\(endTimestamp)"
|
||||
}
|
||||
|
||||
return .init()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isNumericFilter: Bool {
|
||||
switch(self){
|
||||
case .story, .comment:
|
||||
return false
|
||||
case .dateRange:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var isTagFilter: Bool {
|
||||
!isNumericFilter
|
||||
}
|
||||
}
|
62
ios/StoryWidget/Models/SearchParams.swift
Normal file
62
ios/StoryWidget/Models/SearchParams.swift
Normal file
@ -0,0 +1,62 @@
|
||||
import Foundation
|
||||
|
||||
public class SearchParams: Equatable {
|
||||
public let page: Int
|
||||
public let query: String
|
||||
public let sorted: Bool
|
||||
public let filters: Set<SearchFilter>
|
||||
|
||||
public var filteredQuery: String {
|
||||
var buffer = String()
|
||||
|
||||
if sorted {
|
||||
buffer.append("search_by_date?query=\(query)")
|
||||
} else {
|
||||
buffer.append("search?query=\(query)")
|
||||
}
|
||||
|
||||
if !filters.isEmpty {
|
||||
let numericFilters = filters.filter({ $0.isNumericFilter })
|
||||
let tagFilters = filters.filter({ $0.isTagFilter })
|
||||
|
||||
if !numericFilters.isEmpty {
|
||||
buffer.append("&numericFilters=")
|
||||
for filter in filters.filter({ $0.isNumericFilter }) {
|
||||
buffer.append(filter.query)
|
||||
buffer.append(",")
|
||||
}
|
||||
buffer = String(buffer.dropLast(1))
|
||||
}
|
||||
|
||||
if !tagFilters.isEmpty {
|
||||
buffer.append("&tags=(")
|
||||
for filter in filters.filter({ $0.isTagFilter }) {
|
||||
buffer.append(filter.query)
|
||||
buffer.append(",")
|
||||
}
|
||||
buffer = String(buffer.dropLast(1))
|
||||
buffer.append(")")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
buffer.append("&page=\(page)");
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
public init(page: Int, query: String, sorted: Bool, filters: Set<SearchFilter>) {
|
||||
self.page = page
|
||||
self.query = query
|
||||
self.sorted = sorted
|
||||
self.filters = filters
|
||||
}
|
||||
|
||||
public func copyWith(page: Int? = nil, query: String? = nil, sorted: Bool? = nil, filters: Set<SearchFilter>? = nil) -> SearchParams {
|
||||
return SearchParams(page: page ?? self.page, query: query ?? self.query, sorted: sorted ?? self.sorted, filters: filters ?? self.filters)
|
||||
}
|
||||
|
||||
public static func == (lhs: SearchParams, rhs: SearchParams) -> Bool {
|
||||
return lhs.page == rhs.page && lhs.query == rhs.query && lhs.sorted == rhs.sorted && lhs.filters == rhs.filters
|
||||
}
|
||||
}
|
98
ios/StoryWidget/Models/Story.swift
Normal file
98
ios/StoryWidget/Models/Story.swift
Normal file
@ -0,0 +1,98 @@
|
||||
public extension Story {
|
||||
var shortMetadata: String {
|
||||
if isJob {
|
||||
return "\(timeAgo)"
|
||||
} else {
|
||||
return "\(score.orZero) | \(descendants.orZero) | \(timeAgo)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Story: Item {
|
||||
public let id: Int
|
||||
public let parent: Int?
|
||||
public let title: String?
|
||||
public let text: String?
|
||||
public let url: String?
|
||||
public let type: String?
|
||||
public let by: String?
|
||||
public let score: Int?
|
||||
public let descendants: Int?
|
||||
public let time: Int
|
||||
public let kids: [Int]?
|
||||
public var metadata: String {
|
||||
if isJob {
|
||||
return "\(timeAgo) by \(by.orEmpty)"
|
||||
} else {
|
||||
return "\(score.orZero) pt\(score.orZero > 1 ? "s":"") | \(descendants.orZero) cmt\(descendants.orZero > 1 ? "s":"") | \(timeAgo) by \(by.orEmpty)"
|
||||
}
|
||||
}
|
||||
|
||||
public init(id: Int,
|
||||
parent: Int? = nil,
|
||||
title: String?,
|
||||
text: String?,
|
||||
url: String?,
|
||||
type: String?,
|
||||
by: String?,
|
||||
score: Int?,
|
||||
descendants: Int?,
|
||||
time: Int,
|
||||
kids: [Int]? = [Int]()) {
|
||||
self.id = id
|
||||
self.parent = parent
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.url = url
|
||||
self.type = type
|
||||
self.score = score
|
||||
self.by = by
|
||||
self.descendants = descendants
|
||||
self.time = time
|
||||
self.kids = kids
|
||||
}
|
||||
|
||||
// Empty initializer
|
||||
public init() {
|
||||
self.init(
|
||||
id: 0,
|
||||
parent: 0,
|
||||
title: "",
|
||||
text: "",
|
||||
url: "",
|
||||
type: "story",
|
||||
by: "",
|
||||
score: 0,
|
||||
descendants: 0,
|
||||
time: 0
|
||||
)
|
||||
}
|
||||
|
||||
func copyWith(text: String?) -> Story {
|
||||
.init(
|
||||
id: id,
|
||||
parent: parent,
|
||||
title: title,
|
||||
text: text,
|
||||
url: url,
|
||||
type: type,
|
||||
by: by,
|
||||
score: score,
|
||||
descendants: descendants,
|
||||
time: time,
|
||||
kids: kids
|
||||
)
|
||||
}
|
||||
|
||||
public static let errorPlaceholder: Story = .init(
|
||||
id: 0,
|
||||
title: "Something went wrong...",
|
||||
text: nil,
|
||||
url: "retrying...",
|
||||
type: "story",
|
||||
by: nil,
|
||||
score: nil,
|
||||
descendants: nil,
|
||||
time: 0
|
||||
)
|
||||
}
|
56
ios/StoryWidget/Models/StoryType.swift
Normal file
56
ios/StoryWidget/Models/StoryType.swift
Normal file
@ -0,0 +1,56 @@
|
||||
import AppIntents
|
||||
import SwiftData
|
||||
|
||||
public enum StoryType: String, Equatable, CaseIterable, AppEnum, Codable {
|
||||
case top = "top"
|
||||
case best = "best"
|
||||
case new = "new"
|
||||
case ask = "ask"
|
||||
case show = "show"
|
||||
case jobs = "job"
|
||||
|
||||
public var icon: String {
|
||||
switch self {
|
||||
case .top:
|
||||
return "flame"
|
||||
case .best:
|
||||
return "medal"
|
||||
case .new:
|
||||
return "rectangle.dashed"
|
||||
case .ask:
|
||||
return "questionmark.bubble"
|
||||
case .show:
|
||||
return "sparkles.tv"
|
||||
case .jobs:
|
||||
return "briefcase"
|
||||
}
|
||||
}
|
||||
|
||||
public var label: String {
|
||||
switch self {
|
||||
case .jobs:
|
||||
return "jobs"
|
||||
default:
|
||||
return self.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public var isDownloadable: Bool {
|
||||
switch self {
|
||||
case .top, .ask, .best:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public static var typeDisplayRepresentation: TypeDisplayRepresentation = "Story Type"
|
||||
public static var caseDisplayRepresentations: [StoryType : DisplayRepresentation] = [
|
||||
.top: "Top",
|
||||
.best: "Best",
|
||||
.new: "New",
|
||||
.ask: "Ask HN",
|
||||
.show: "Show HN",
|
||||
.jobs: "YC Jobs"
|
||||
]
|
||||
}
|
53
ios/StoryWidget/Models/User.swift
Normal file
53
ios/StoryWidget/Models/User.swift
Normal file
@ -0,0 +1,53 @@
|
||||
import Foundation
|
||||
|
||||
public struct User: Decodable, Equatable {
|
||||
public let id: String?
|
||||
public let about: String?
|
||||
public let created: Int?
|
||||
public let delay: Int?
|
||||
public let karma: Int?
|
||||
public let submitted: [Int]?
|
||||
|
||||
public init() {
|
||||
self.id = .init()
|
||||
self.about = .init()
|
||||
self.created = .init()
|
||||
self.delay = .init()
|
||||
self.karma = .init()
|
||||
self.submitted = .init()
|
||||
}
|
||||
|
||||
/// If a user does not have any activity, the user endpoint will not return anything.
|
||||
/// in that case, we create a user with only username.
|
||||
public init(id: String) {
|
||||
self.id = id
|
||||
self.about = .init()
|
||||
self.created = .init()
|
||||
self.delay = .init()
|
||||
self.karma = .init()
|
||||
self.submitted = .init()
|
||||
}
|
||||
|
||||
init(id: String?, about: String?, created: Int?, delay: Int?, karma: Int?, submitted: [Int]?) {
|
||||
self.id = id
|
||||
self.about = about
|
||||
self.created = created
|
||||
self.delay = delay
|
||||
self.karma = karma
|
||||
self.submitted = submitted
|
||||
}
|
||||
|
||||
func copyWith(about: String? = nil) -> User {
|
||||
return User(id: id, about: about ?? self.about, created: created, delay: delay, karma: karma, submitted: submitted)
|
||||
}
|
||||
}
|
||||
|
||||
public extension User {
|
||||
var createdAt: String? {
|
||||
guard let created = created else { return nil }
|
||||
let date = Date(timeIntervalSince1970: Double(created))
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "MMM d, yyyy"
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
}
|
17
ios/StoryWidget/SelectStoryTypeIntent.swift
Normal file
17
ios/StoryWidget/SelectStoryTypeIntent.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import AppIntents
|
||||
|
||||
struct SelectStoryTypeIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource = "Select Story Type"
|
||||
static var description = IntentDescription("Select the type of story you want to see.")
|
||||
|
||||
@Parameter(title: "Story Type", default: StorySource.top)
|
||||
var source: StorySource
|
||||
|
||||
init(source: StorySource) {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
init() {
|
||||
self.source = .top
|
||||
}
|
||||
}
|
14
ios/StoryWidget/StoryEntry.swift
Normal file
14
ios/StoryWidget/StoryEntry.swift
Normal file
@ -0,0 +1,14 @@
|
||||
import WidgetKit
|
||||
import Foundation
|
||||
|
||||
struct StoryEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let story: Story
|
||||
let source: StorySource
|
||||
|
||||
static let errorPlaceholder: StoryEntry = StoryEntry(
|
||||
date: .now,
|
||||
story: .errorPlaceholder,
|
||||
source: .top
|
||||
)
|
||||
}
|
130
ios/StoryWidget/StoryRepository.swift
Normal file
130
ios/StoryWidget/StoryRepository.swift
Normal file
@ -0,0 +1,130 @@
|
||||
import Foundation
|
||||
import Alamofire
|
||||
|
||||
public class StoryRepository {
|
||||
public static let shared: StoryRepository = .init()
|
||||
|
||||
private let baseUrl: String = "https://hacker-news.firebaseio.com/v0/"
|
||||
|
||||
private init() {}
|
||||
|
||||
// MARK: - Story related.
|
||||
|
||||
public func fetchAllStories(from storyType: StoryType, onStoryFetched: @escaping (Story) -> Void) async -> Void {
|
||||
let storyIds = await fetchStoryIds(from: storyType)
|
||||
for id in storyIds {
|
||||
let story = await self.fetchStory(id)
|
||||
if let story = story {
|
||||
onStoryFetched(story)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchStoryIds(from storyType: StoryType) async -> [Int] {
|
||||
let response = await AF.request("\(self.baseUrl)\(storyType.rawValue)stories.json").serializingString().response
|
||||
guard response.data != nil else { return [Int]() }
|
||||
let storyIds = try? JSONDecoder().decode([Int].self, from: response.data!)
|
||||
return storyIds ?? [Int]()
|
||||
}
|
||||
|
||||
public func fetchStoryIds(from storyType: String) async -> [Int] {
|
||||
let response = await AF.request("\(self.baseUrl)\(storyType)stories.json").serializingString().response
|
||||
guard response.data != nil else { return [Int]() }
|
||||
let storyIds = try? JSONDecoder().decode([Int].self, from: response.data!)
|
||||
return storyIds ?? [Int]()
|
||||
}
|
||||
|
||||
public func fetchStories(ids: [Int], onStoryFetched: @escaping (Story) -> Void) async -> Void {
|
||||
for id in ids {
|
||||
let story = await fetchStory(id)
|
||||
if let story = story {
|
||||
onStoryFetched(story)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchStory(_ id: Int) async -> Story?{
|
||||
let response = await AF.request("\(self.baseUrl)item/\(id).json").serializingString().response
|
||||
if let data = response.data,
|
||||
var story = try? JSONDecoder().decode(Story.self, from: data) {
|
||||
let formattedText = story.text.htmlStripped
|
||||
story = story.copyWith(text: formattedText)
|
||||
return story
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Comment related.
|
||||
|
||||
public func fetchComments(ids: [Int], onCommentFetched: @escaping (Comment) -> Void) async -> Void {
|
||||
for id in ids {
|
||||
let comment = await fetchComment(id)
|
||||
if let comment = comment {
|
||||
onCommentFetched(comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchComment(_ id: Int) async -> Comment? {
|
||||
let response = await AF.request("\(self.baseUrl)item/\(id).json").serializingString().response
|
||||
if let data = response.data,
|
||||
var comment = try? JSONDecoder().decode(Comment.self, from: data) {
|
||||
let formattedText = comment.text.htmlStripped
|
||||
comment = comment.copyWith(text: formattedText)
|
||||
return comment
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Item related.
|
||||
|
||||
public func fetchItems(ids: [Int], filtered: Bool = true, onItemFetched: @escaping (any Item) -> Void) async -> Void {
|
||||
for id in ids {
|
||||
let item = await fetchItem(id)
|
||||
guard let item = item else { continue }
|
||||
if let story = item as? Story {
|
||||
onItemFetched(story)
|
||||
} else if let cmt = item as? Comment {
|
||||
onItemFetched(cmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchItem(_ id: Int) async -> (any Item)? {
|
||||
let response = await AF.request("\(self.baseUrl)item/\(id).json").serializingString().response
|
||||
if let data = response.data,
|
||||
let result = try? response.result.get(),
|
||||
let map = result.toJSON() as? [String: AnyObject],
|
||||
let type = map["type"] as? String {
|
||||
switch type {
|
||||
case "story":
|
||||
let story = try? JSONDecoder().decode(Story.self, from: data)
|
||||
let formattedText = story?.text.htmlStripped
|
||||
return story?.copyWith(text: formattedText)
|
||||
case "comment":
|
||||
let comment = try? JSONDecoder().decode(Comment.self, from: data)
|
||||
let formattedText = comment?.text.htmlStripped
|
||||
return comment?.copyWith(text: formattedText)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - User related.
|
||||
|
||||
public func fetchUser(_ id: String) async -> User? {
|
||||
let response = await AF.request("\(self.baseUrl)/user/\(id).json").serializingString().response
|
||||
if let data = response.data,
|
||||
let user = try? JSONDecoder().decode(User.self, from: data) {
|
||||
let formattedText = user.about.orEmpty.htmlStripped
|
||||
return user.copyWith(about: formattedText)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
21
ios/StoryWidget/StorySource.swift
Normal file
21
ios/StoryWidget/StorySource.swift
Normal file
@ -0,0 +1,21 @@
|
||||
import AppIntents
|
||||
|
||||
enum StorySource: String, AppEnum {
|
||||
case top
|
||||
case best
|
||||
case new
|
||||
case ask
|
||||
case show
|
||||
case job
|
||||
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Story Source"
|
||||
|
||||
static var caseDisplayRepresentations: [StorySource : DisplayRepresentation] = [
|
||||
.top: "Top",
|
||||
.best: "Best",
|
||||
.new: "New",
|
||||
.ask: "Ask",
|
||||
.show: "Show",
|
||||
.job: "Jobs"
|
||||
]
|
||||
}
|
42
ios/StoryWidget/StoryTimelineProvider.swift
Normal file
42
ios/StoryWidget/StoryTimelineProvider.swift
Normal file
@ -0,0 +1,42 @@
|
||||
import WidgetKit
|
||||
|
||||
struct StoryTimelineProvider: AppIntentTimelineProvider {
|
||||
func snapshot(for configuration: SelectStoryTypeIntent, in context: Context) async -> StoryEntry {
|
||||
let ids = await StoryRepository.shared.fetchStoryIds(from: configuration.source.rawValue)
|
||||
guard let first = ids.first else { return .errorPlaceholder }
|
||||
let story = await StoryRepository.shared.fetchStory(first)
|
||||
guard let story = story else { return .errorPlaceholder }
|
||||
let entry = StoryEntry(date: Date(), story: story, source: configuration.source)
|
||||
return entry
|
||||
}
|
||||
|
||||
func placeholder(in context: Context) -> StoryEntry {
|
||||
let story = Story(
|
||||
id: 0,
|
||||
title: "This is a placeholder story",
|
||||
text: "text",
|
||||
url: "",
|
||||
type: "story",
|
||||
by: "Hacki",
|
||||
score: 100,
|
||||
descendants: 24,
|
||||
time: Int(Date().timeIntervalSince1970)
|
||||
)
|
||||
return StoryEntry(date: Date(), story: story, source: .top)
|
||||
}
|
||||
|
||||
func timeline(for configuration: SelectStoryTypeIntent, in context: Context) async -> Timeline<StoryEntry> {
|
||||
let ids = await StoryRepository.shared.fetchStoryIds(from: configuration.source.rawValue)
|
||||
guard let first = ids.first else {
|
||||
return Timeline(entries: [.errorPlaceholder], policy: .atEnd)
|
||||
}
|
||||
let story = await StoryRepository.shared.fetchStory(first)
|
||||
guard let story = story else {
|
||||
return Timeline(entries: [.errorPlaceholder], policy: .atEnd)
|
||||
}
|
||||
let entry = StoryEntry(date: Date(), story: story, source: configuration.source)
|
||||
|
||||
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||
return timeline
|
||||
}
|
||||
}
|
84
ios/StoryWidget/StoryWidget.swift
Normal file
84
ios/StoryWidget/StoryWidget.swift
Normal file
@ -0,0 +1,84 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import AppIntents
|
||||
|
||||
struct StoryWidgetView : View {
|
||||
@Environment(\.widgetFamily) var family
|
||||
@Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
|
||||
var story: Story
|
||||
var source: StorySource
|
||||
|
||||
var body: some View {
|
||||
switch family {
|
||||
case .accessoryRectangular:
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(story.shortMetadata)
|
||||
.font(.caption)
|
||||
Text(story.title.orEmpty)
|
||||
.font(.caption).fontWeight(.bold)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.containerBackground(for: .widget) {
|
||||
Color(UIColor.secondarySystemBackground)
|
||||
}
|
||||
.widgetURL(URL(string: "\(story.id)"))
|
||||
default:
|
||||
HStack {
|
||||
VStack {
|
||||
Text(story.title.orEmpty)
|
||||
.font(family == .systemSmall ? .system(size: 14) : .body)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
if let text = story.text, text.isNotEmpty {
|
||||
HStack {
|
||||
Text(text.replacingOccurrences(of: "\n", with: " "))
|
||||
.font(.footnote)
|
||||
.lineLimit(3)
|
||||
.foregroundColor(.gray)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
HStack {
|
||||
if let url = story.readableUrl {
|
||||
Text(url)
|
||||
.font(family == .systemSmall ? .system(size: 8) : .footnote)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Divider().frame(maxWidth: .infinity)
|
||||
HStack(alignment: .center) {
|
||||
Text("\(source.rawValue.uppercased()) | \(story.metadata)")
|
||||
.font(family == .systemSmall ? showsWidgetContainerBackground ? .system(size: 10) : .system(size: 8) : .caption)
|
||||
.padding(.top, showsWidgetContainerBackground ? 2 : .zero)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.all, showsWidgetContainerBackground ? .zero : nil)
|
||||
.containerBackground(for: .widget) {
|
||||
Color(UIColor.secondarySystemBackground)
|
||||
}
|
||||
.widgetURL(URL(string: "\(story.id)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StoryWidget: Widget {
|
||||
let kind: String = "StoryWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: SelectStoryTypeIntent.self,
|
||||
provider: StoryTimelineProvider()) { entry in
|
||||
StoryWidgetView(story: entry.story, source: entry.source)
|
||||
}
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .accessoryRectangular])
|
||||
.configurationDisplayName("Story on Hacker News")
|
||||
.description("Watch out. It's hot.")
|
||||
}
|
||||
}
|
9
ios/StoryWidget/StoryWidgetBundle.swift
Normal file
9
ios/StoryWidget/StoryWidgetBundle.swift
Normal file
@ -0,0 +1,9 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct StoryWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
StoryWidget()
|
||||
}
|
||||
}
|
8
ios/StoryWidget/Timeline+Placeholder.swift
Normal file
8
ios/StoryWidget/Timeline+Placeholder.swift
Normal file
@ -0,0 +1,8 @@
|
||||
import WidgetKit
|
||||
|
||||
extension Timeline where EntryType == StoryEntry {
|
||||
static let errorPlaceholder: Timeline<StoryEntry> = .init(
|
||||
entries: [.errorPlaceholder],
|
||||
policy: .atEnd
|
||||
)
|
||||
}
|
18
ios/Widget/SelectStoryTypeIntent.swift
Normal file
18
ios/Widget/SelectStoryTypeIntent.swift
Normal file
@ -0,0 +1,18 @@
|
||||
import AppIntents
|
||||
import HackerNewsKit
|
||||
|
||||
struct SelectStoryTypeIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource = "Select Story Type"
|
||||
static var description = IntentDescription("Select the type of story you want to see.")
|
||||
|
||||
@Parameter(title: "Story Type", default: StorySource.top)
|
||||
var source: StorySource
|
||||
|
||||
init(source: StorySource) {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
init() {
|
||||
self.source = .top
|
||||
}
|
||||
}
|
21
ios/Widget/StorySource.swift
Normal file
21
ios/Widget/StorySource.swift
Normal file
@ -0,0 +1,21 @@
|
||||
import AppIntents
|
||||
|
||||
enum StorySource: String, AppEnum {
|
||||
case top
|
||||
case best
|
||||
case new
|
||||
case ask
|
||||
case show
|
||||
case job
|
||||
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Story Source"
|
||||
|
||||
static var caseDisplayRepresentations: [StorySource : DisplayRepresentation] = [
|
||||
.top: "Top",
|
||||
.best: "Best",
|
||||
.new: "New",
|
||||
.ask: "Ask",
|
||||
.show: "Show",
|
||||
.job: "Jobs"
|
||||
]
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
app_identifier("com.jiaqi.hacki") # The bundle identifier of your app
|
||||
apple_id("georgefung78@Live.com") # Your Apple Developer Portal username
|
||||
apple_id("georgefung78@live.com") # Your Apple Developer Portal username
|
||||
|
||||
itc_team_id("120097292") # App Store Connect Team ID
|
||||
team_id("QMWX3X2NF7") # Developer Portal Team ID
|
||||
|
@ -34,7 +34,7 @@ platform :ios do
|
||||
# Download code signing certificates using `match` (and the `MATCH_PASSWORD` secret)
|
||||
sync_code_signing(
|
||||
type: "appstore",
|
||||
app_identifier: [APP_IDENTIFIER, "#{APP_IDENTIFIER}.Share-Extension", "#{APP_IDENTIFIER}.Action-Extension"],
|
||||
app_identifier: [APP_IDENTIFIER, "#{APP_IDENTIFIER}.Share-Extension", "#{APP_IDENTIFIER}.Action-Extension", "#{APP_IDENTIFIER}.Widget-Extension"],
|
||||
readonly: true
|
||||
)
|
||||
|
||||
|
11
pubspec.lock
11
pubspec.lock
@ -297,12 +297,11 @@ packages:
|
||||
feature_discovery:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: bcf4ef28542acb0c98ec7dfa9d20d8fa7a0a594b
|
||||
resolved-ref: bcf4ef28542acb0c98ec7dfa9d20d8fa7a0a594b
|
||||
url: "https://github.com/livinglist/feature_discovery"
|
||||
source: git
|
||||
version: "0.14.1"
|
||||
name: feature_discovery
|
||||
sha256: "54c2e0c7cc229c4a7149ef21d933fa7115cf034b985926477488aa2d0619321b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.2"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -21,10 +21,7 @@ dependencies:
|
||||
dio_smart_retry: ^7.0.1
|
||||
equatable: ^2.0.5
|
||||
fast_gbk: ^1.0.0
|
||||
feature_discovery:
|
||||
git:
|
||||
url: https://github.com/livinglist/feature_discovery
|
||||
ref: bcf4ef28542acb0c98ec7dfa9d20d8fa7a0a594b
|
||||
feature_discovery: ^0.14.2
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: ^9.0.0
|
||||
|
Reference in New Issue
Block a user