├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── .gitignore ├── Extensions └── UserDefaults+Extension.swift ├── Homepage.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Homepage ├── App Intents │ ├── OpenAppIntent.swift │ ├── SetHomepageIntent.swift │ └── ShortcutsProvider.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── AppIcon.png │ │ ├── Contents.json │ │ ├── DarkIcon.png │ │ └── TintedIcon.png │ ├── AppImage.imageset │ │ ├── AppIcon.png │ │ ├── Contents.json │ │ └── DarkIcon.png │ └── Contents.json ├── Extensions │ ├── Bundle+Extension.swift │ └── UIApplication+Extension.swift ├── Helpers │ └── URLValidator.swift ├── Homepage.entitlements ├── HomepageApp.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Resources │ └── Localizable.xcstrings ├── Settings.bundle │ ├── Root.plist │ └── en.lproj │ │ └── Root.strings ├── View Modifiers │ └── Backport.swift └── Views │ └── ContentView.swift ├── HomepageExtension ├── HomepageExtension.entitlements ├── Info.plist ├── Resources │ ├── _locales │ │ └── en │ │ │ └── messages.json │ ├── background.js │ ├── default.html │ ├── homepage.html │ ├── homepage.js │ ├── images │ │ ├── icon-128.png │ │ ├── icon-256.png │ │ ├── icon-48.png │ │ ├── icon-512.png │ │ ├── icon-64.png │ │ ├── icon-96.png │ │ ├── toolbar-icon-16.png │ │ ├── toolbar-icon-19.png │ │ ├── toolbar-icon-32.png │ │ ├── toolbar-icon-38.png │ │ ├── toolbar-icon-48.png │ │ └── toolbar-icon-72.png │ ├── localize.js │ ├── manifest.json │ ├── styles.css │ └── translations.json └── SafariWebExtensionHandler.swift ├── HomepageTests ├── BundleExtensionTests.swift └── URLValidatorTests.swift ├── HomepageUITests ├── HomepageUITests.swift └── HomepageUITestsLaunchTests.swift ├── HomepageWidget ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── HouseGear.symbolset │ │ ├── Contents.json │ │ └── HouseGear.svg │ └── WidgetBackground.colorset │ │ └── Contents.json ├── HomepageWidgetBundle.swift ├── Info.plist ├── Localizable.xcstrings └── SetHomepageButton.swift ├── LICENSE ├── README.md ├── Resources └── PrivacyInfo.xcprivacy └── ci_scripts └── ci_post_clone.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: ahnafm 2 | github: infinitepower18 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help improve Homepage for Safari 3 | labels: bug 4 | 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the bug 10 | description: A clear and concise description of what the bug is. Include screenshots if possible. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: reproduce 15 | attributes: 16 | label: Steps to reproduce 17 | description: Include steps to reproduce the behavior. 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: expected 22 | attributes: 23 | label: Expected behavior 24 | description: A clear and concise description of what you expected to happen. 25 | validations: 26 | required: true 27 | - type: checkboxes 28 | id: configuration 29 | attributes: 30 | label: Extension configuration 31 | description: Please check that the extension is enabled and it is set under Open New Tabs in Safari Settings. 32 | options: 33 | - label: The extension is configured correctly 34 | required: true 35 | - type: dropdown 36 | id: device 37 | attributes: 38 | label: Device 39 | description: What device are you using? 40 | options: 41 | - iPhone 42 | - iPad 43 | - Apple Vision Pro 44 | - iPod touch 45 | - Mac 46 | validations: 47 | required: true 48 | - type: input 49 | id: osversion 50 | attributes: 51 | label: OS version 52 | validations: 53 | required: true 54 | - type: input 55 | id: appversion 56 | attributes: 57 | label: App version 58 | validations: 59 | required: true 60 | - type: textarea 61 | id: other 62 | attributes: 63 | label: Additional context 64 | description: Add any other context about the problem here. 65 | validations: 66 | required: false 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Known issues 4 | url: https://github.com/infinitepower18/Homepage-MobileSafari/discussions/17 5 | about: Before posting, check the known issues to see if your issue is already listed. 6 | - name: General support 7 | url: https://github.com/infinitepower18/Homepage-MobileSafari/discussions 8 | about: For support queries, please post in the project's discussion. 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Extensions/UserDefaults+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults+Extension.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 11/06/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | extension UserDefaults { 11 | /// The app group to use for UserDefaults 12 | nonisolated(unsafe) static let group = UserDefaults(suiteName: "group.com.ip18.Homepage") 13 | 14 | /// The user defined homepage URL 15 | static var homepage: String? { 16 | get { 17 | UserDefaults.group?.string(forKey: "homepage") 18 | } 19 | set { 20 | UserDefaults.group?.set(newValue, forKey: "homepage") 21 | } 22 | } 23 | 24 | /// Whether the URL should be cleared 25 | static var clearUrl: Bool { 26 | get { 27 | UserDefaults.standard.bool(forKey: "clear_url") 28 | } 29 | set { 30 | UserDefaults.standard.set(newValue, forKey: "clear_url") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Homepage.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 70; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9CBD9AFA2C90BB6B00F9EE29 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CBD9AF92C90BB6B00F9EE29 /* WidgetKit.framework */; }; 11 | 9CBD9AFC2C90BB6B00F9EE29 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CBD9AFB2C90BB6B00F9EE29 /* SwiftUI.framework */; }; 12 | 9CBD9B092C90BB6C00F9EE29 /* HomepageWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9CBD9AF72C90BB6B00F9EE29 /* HomepageWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 13 | 9CEA5B3B2BBF3FE500ACF6DC /* HomepageExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9CEA5B232BBF3FE500ACF6DC /* HomepageExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXContainerItemProxy section */ 17 | 9C93CDB22D4ECC0300B8F49A /* PBXContainerItemProxy */ = { 18 | isa = PBXContainerItemProxy; 19 | containerPortal = 9CC4B4EC2BBEF5B4007BC68B /* Project object */; 20 | proxyType = 1; 21 | remoteGlobalIDString = 9CC4B4F32BBEF5B4007BC68B; 22 | remoteInfo = Homepage; 23 | }; 24 | 9C9955822C18C02100EB9E17 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 9CC4B4EC2BBEF5B4007BC68B /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 9CC4B4F32BBEF5B4007BC68B; 29 | remoteInfo = Homepage; 30 | }; 31 | 9CBD9B072C90BB6C00F9EE29 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 9CC4B4EC2BBEF5B4007BC68B /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 9CBD9AF62C90BB6B00F9EE29; 36 | remoteInfo = HomepageWidgetExtension; 37 | }; 38 | 9CEA5B392BBF3FE500ACF6DC /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 9CC4B4EC2BBEF5B4007BC68B /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 9CEA5B222BBF3FE500ACF6DC; 43 | remoteInfo = HomepageExtension; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXCopyFilesBuildPhase section */ 48 | 9CC4B5262BBEF69C007BC68B /* Embed Foundation Extensions */ = { 49 | isa = PBXCopyFilesBuildPhase; 50 | buildActionMask = 2147483647; 51 | dstPath = ""; 52 | dstSubfolderSpec = 13; 53 | files = ( 54 | 9CBD9B092C90BB6C00F9EE29 /* HomepageWidgetExtension.appex in Embed Foundation Extensions */, 55 | 9CEA5B3B2BBF3FE500ACF6DC /* HomepageExtension.appex in Embed Foundation Extensions */, 56 | ); 57 | name = "Embed Foundation Extensions"; 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXCopyFilesBuildPhase section */ 61 | 62 | /* Begin PBXFileReference section */ 63 | 9C93CDAC2D4ECC0300B8F49A /* HomepageUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HomepageUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 9C99557E2C18C02100EB9E17 /* HomepageTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HomepageTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 9CBD9AF72C90BB6B00F9EE29 /* HomepageWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HomepageWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 9CBD9AF92C90BB6B00F9EE29 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 67 | 9CBD9AFB2C90BB6B00F9EE29 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 68 | 9CC4B4F42BBEF5B4007BC68B /* Homepage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Homepage.app; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 9CEA5B232BBF3FE500ACF6DC /* HomepageExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HomepageExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 73 | 9C261F892CE0F75600DA6509 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 74 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 75 | membershipExceptions = ( 76 | Info.plist, 77 | ); 78 | target = 9CC4B4F32BBEF5B4007BC68B /* Homepage */; 79 | }; 80 | 9C261F8A2CE0F75600DA6509 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 81 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 82 | membershipExceptions = ( 83 | Info.plist, 84 | ); 85 | target = 9CEA5B222BBF3FE500ACF6DC /* HomepageExtension */; 86 | }; 87 | 9C261F8B2CE0F75600DA6509 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 88 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 89 | membershipExceptions = ( 90 | "App Intents/OpenAppIntent.swift", 91 | ); 92 | target = 9CBD9AF62C90BB6B00F9EE29 /* HomepageWidgetExtension */; 93 | }; 94 | 9CBD9B0C2C90BB6C00F9EE29 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 95 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 96 | membershipExceptions = ( 97 | Info.plist, 98 | ); 99 | target = 9CBD9AF62C90BB6B00F9EE29 /* HomepageWidgetExtension */; 100 | }; 101 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 102 | 103 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 104 | 9C261F392CE0F75600DA6509 /* Extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Extensions; sourceTree = ""; }; 105 | 9C261F532CE0F75600DA6509 /* Homepage */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (9C261F892CE0F75600DA6509 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 9C261F8B2CE0F75600DA6509 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Homepage; sourceTree = ""; }; 106 | 9C261F732CE0F75600DA6509 /* HomepageExtension */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (9C261F8A2CE0F75600DA6509 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (Resources/_locales, Resources/images, ); path = HomepageExtension; sourceTree = ""; }; 107 | 9C261F812CE0F75600DA6509 /* Resources */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Resources; sourceTree = ""; }; 108 | 9C261F862CE0F75600DA6509 /* HomepageTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = HomepageTests; sourceTree = ""; }; 109 | 9C93CDAD2D4ECC0300B8F49A /* HomepageUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = HomepageUITests; sourceTree = ""; }; 110 | 9CBD9AFD2C90BB6B00F9EE29 /* HomepageWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (9CBD9B0C2C90BB6C00F9EE29 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = HomepageWidget; sourceTree = ""; }; 111 | /* End PBXFileSystemSynchronizedRootGroup section */ 112 | 113 | /* Begin PBXFrameworksBuildPhase section */ 114 | 9C93CDA92D4ECC0300B8F49A /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | 9C99557B2C18C02100EB9E17 /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | 9CBD9AF42C90BB6B00F9EE29 /* Frameworks */ = { 129 | isa = PBXFrameworksBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | 9CBD9AFC2C90BB6B00F9EE29 /* SwiftUI.framework in Frameworks */, 133 | 9CBD9AFA2C90BB6B00F9EE29 /* WidgetKit.framework in Frameworks */, 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | 9CC4B4F12BBEF5B4007BC68B /* Frameworks */ = { 138 | isa = PBXFrameworksBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | 9CEA5B202BBF3FE500ACF6DC /* Frameworks */ = { 145 | isa = PBXFrameworksBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXFrameworksBuildPhase section */ 152 | 153 | /* Begin PBXGroup section */ 154 | 9CBD9AF82C90BB6B00F9EE29 /* Frameworks */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 9CBD9AF92C90BB6B00F9EE29 /* WidgetKit.framework */, 158 | 9CBD9AFB2C90BB6B00F9EE29 /* SwiftUI.framework */, 159 | ); 160 | name = Frameworks; 161 | sourceTree = ""; 162 | }; 163 | 9CC4B4EB2BBEF5B4007BC68B = { 164 | isa = PBXGroup; 165 | children = ( 166 | 9C261F532CE0F75600DA6509 /* Homepage */, 167 | 9C261F732CE0F75600DA6509 /* HomepageExtension */, 168 | 9C261F812CE0F75600DA6509 /* Resources */, 169 | 9C261F392CE0F75600DA6509 /* Extensions */, 170 | 9C261F862CE0F75600DA6509 /* HomepageTests */, 171 | 9CBD9AFD2C90BB6B00F9EE29 /* HomepageWidget */, 172 | 9C93CDAD2D4ECC0300B8F49A /* HomepageUITests */, 173 | 9CBD9AF82C90BB6B00F9EE29 /* Frameworks */, 174 | 9CC4B4F52BBEF5B4007BC68B /* Products */, 175 | ); 176 | sourceTree = ""; 177 | }; 178 | 9CC4B4F52BBEF5B4007BC68B /* Products */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 9CC4B4F42BBEF5B4007BC68B /* Homepage.app */, 182 | 9CEA5B232BBF3FE500ACF6DC /* HomepageExtension.appex */, 183 | 9C99557E2C18C02100EB9E17 /* HomepageTests.xctest */, 184 | 9CBD9AF72C90BB6B00F9EE29 /* HomepageWidgetExtension.appex */, 185 | 9C93CDAC2D4ECC0300B8F49A /* HomepageUITests.xctest */, 186 | ); 187 | name = Products; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXGroup section */ 191 | 192 | /* Begin PBXNativeTarget section */ 193 | 9C93CDAB2D4ECC0300B8F49A /* HomepageUITests */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = 9C93CDB62D4ECC0300B8F49A /* Build configuration list for PBXNativeTarget "HomepageUITests" */; 196 | buildPhases = ( 197 | 9C93CDA82D4ECC0300B8F49A /* Sources */, 198 | 9C93CDA92D4ECC0300B8F49A /* Frameworks */, 199 | 9C93CDAA2D4ECC0300B8F49A /* Resources */, 200 | ); 201 | buildRules = ( 202 | ); 203 | dependencies = ( 204 | 9C93CDB32D4ECC0300B8F49A /* PBXTargetDependency */, 205 | ); 206 | fileSystemSynchronizedGroups = ( 207 | 9C93CDAD2D4ECC0300B8F49A /* HomepageUITests */, 208 | ); 209 | name = HomepageUITests; 210 | packageProductDependencies = ( 211 | ); 212 | productName = HomepageUITests; 213 | productReference = 9C93CDAC2D4ECC0300B8F49A /* HomepageUITests.xctest */; 214 | productType = "com.apple.product-type.bundle.ui-testing"; 215 | }; 216 | 9C99557D2C18C02100EB9E17 /* HomepageTests */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 9C9955842C18C02100EB9E17 /* Build configuration list for PBXNativeTarget "HomepageTests" */; 219 | buildPhases = ( 220 | 9C99557A2C18C02100EB9E17 /* Sources */, 221 | 9C99557B2C18C02100EB9E17 /* Frameworks */, 222 | 9C99557C2C18C02100EB9E17 /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | 9C9955832C18C02100EB9E17 /* PBXTargetDependency */, 228 | ); 229 | fileSystemSynchronizedGroups = ( 230 | 9C261F862CE0F75600DA6509 /* HomepageTests */, 231 | ); 232 | name = HomepageTests; 233 | productName = HomepageTests; 234 | productReference = 9C99557E2C18C02100EB9E17 /* HomepageTests.xctest */; 235 | productType = "com.apple.product-type.bundle.unit-test"; 236 | }; 237 | 9CBD9AF62C90BB6B00F9EE29 /* HomepageWidgetExtension */ = { 238 | isa = PBXNativeTarget; 239 | buildConfigurationList = 9CBD9B0D2C90BB6C00F9EE29 /* Build configuration list for PBXNativeTarget "HomepageWidgetExtension" */; 240 | buildPhases = ( 241 | 9CBD9AF32C90BB6B00F9EE29 /* Sources */, 242 | 9CBD9AF42C90BB6B00F9EE29 /* Frameworks */, 243 | 9CBD9AF52C90BB6B00F9EE29 /* Resources */, 244 | ); 245 | buildRules = ( 246 | ); 247 | dependencies = ( 248 | ); 249 | fileSystemSynchronizedGroups = ( 250 | 9CBD9AFD2C90BB6B00F9EE29 /* HomepageWidget */, 251 | ); 252 | name = HomepageWidgetExtension; 253 | packageProductDependencies = ( 254 | ); 255 | productName = HomepageWidgetExtension; 256 | productReference = 9CBD9AF72C90BB6B00F9EE29 /* HomepageWidgetExtension.appex */; 257 | productType = "com.apple.product-type.app-extension"; 258 | }; 259 | 9CC4B4F32BBEF5B4007BC68B /* Homepage */ = { 260 | isa = PBXNativeTarget; 261 | buildConfigurationList = 9CC4B5022BBEF5B5007BC68B /* Build configuration list for PBXNativeTarget "Homepage" */; 262 | buildPhases = ( 263 | 9CC4B4F02BBEF5B4007BC68B /* Sources */, 264 | 9CC4B4F12BBEF5B4007BC68B /* Frameworks */, 265 | 9CC4B4F22BBEF5B4007BC68B /* Resources */, 266 | 9CC4B5262BBEF69C007BC68B /* Embed Foundation Extensions */, 267 | 9C9955792C18BD7100EB9E17 /* SwiftLint */, 268 | ); 269 | buildRules = ( 270 | ); 271 | dependencies = ( 272 | 9CEA5B3A2BBF3FE500ACF6DC /* PBXTargetDependency */, 273 | 9CBD9B082C90BB6C00F9EE29 /* PBXTargetDependency */, 274 | ); 275 | fileSystemSynchronizedGroups = ( 276 | 9C261F392CE0F75600DA6509 /* Extensions */, 277 | 9C261F532CE0F75600DA6509 /* Homepage */, 278 | 9C261F812CE0F75600DA6509 /* Resources */, 279 | ); 280 | name = Homepage; 281 | productName = Homepage; 282 | productReference = 9CC4B4F42BBEF5B4007BC68B /* Homepage.app */; 283 | productType = "com.apple.product-type.application"; 284 | }; 285 | 9CEA5B222BBF3FE500ACF6DC /* HomepageExtension */ = { 286 | isa = PBXNativeTarget; 287 | buildConfigurationList = 9CEA5B3C2BBF3FE500ACF6DC /* Build configuration list for PBXNativeTarget "HomepageExtension" */; 288 | buildPhases = ( 289 | 9CEA5B1F2BBF3FE500ACF6DC /* Sources */, 290 | 9CEA5B202BBF3FE500ACF6DC /* Frameworks */, 291 | 9CEA5B212BBF3FE500ACF6DC /* Resources */, 292 | ); 293 | buildRules = ( 294 | ); 295 | dependencies = ( 296 | ); 297 | fileSystemSynchronizedGroups = ( 298 | 9C261F392CE0F75600DA6509 /* Extensions */, 299 | 9C261F732CE0F75600DA6509 /* HomepageExtension */, 300 | 9C261F812CE0F75600DA6509 /* Resources */, 301 | ); 302 | name = HomepageExtension; 303 | productName = HomepageExtension; 304 | productReference = 9CEA5B232BBF3FE500ACF6DC /* HomepageExtension.appex */; 305 | productType = "com.apple.product-type.app-extension"; 306 | }; 307 | /* End PBXNativeTarget section */ 308 | 309 | /* Begin PBXProject section */ 310 | 9CC4B4EC2BBEF5B4007BC68B /* Project object */ = { 311 | isa = PBXProject; 312 | attributes = { 313 | BuildIndependentTargetsInParallel = 1; 314 | LastSwiftUpdateCheck = 1620; 315 | LastUpgradeCheck = 1600; 316 | TargetAttributes = { 317 | 9C93CDAB2D4ECC0300B8F49A = { 318 | CreatedOnToolsVersion = 16.2; 319 | TestTargetID = 9CC4B4F32BBEF5B4007BC68B; 320 | }; 321 | 9C99557D2C18C02100EB9E17 = { 322 | CreatedOnToolsVersion = 15.4; 323 | TestTargetID = 9CC4B4F32BBEF5B4007BC68B; 324 | }; 325 | 9CBD9AF62C90BB6B00F9EE29 = { 326 | CreatedOnToolsVersion = 16.0; 327 | }; 328 | 9CC4B4F32BBEF5B4007BC68B = { 329 | CreatedOnToolsVersion = 15.3; 330 | }; 331 | 9CEA5B222BBF3FE500ACF6DC = { 332 | CreatedOnToolsVersion = 15.3; 333 | }; 334 | }; 335 | }; 336 | buildConfigurationList = 9CC4B4EF2BBEF5B4007BC68B /* Build configuration list for PBXProject "Homepage" */; 337 | compatibilityVersion = "Xcode 14.0"; 338 | developmentRegion = en; 339 | hasScannedForEncodings = 0; 340 | knownRegions = ( 341 | en, 342 | Base, 343 | ); 344 | mainGroup = 9CC4B4EB2BBEF5B4007BC68B; 345 | packageReferences = ( 346 | ); 347 | productRefGroup = 9CC4B4F52BBEF5B4007BC68B /* Products */; 348 | projectDirPath = ""; 349 | projectRoot = ""; 350 | targets = ( 351 | 9CC4B4F32BBEF5B4007BC68B /* Homepage */, 352 | 9CEA5B222BBF3FE500ACF6DC /* HomepageExtension */, 353 | 9C99557D2C18C02100EB9E17 /* HomepageTests */, 354 | 9CBD9AF62C90BB6B00F9EE29 /* HomepageWidgetExtension */, 355 | 9C93CDAB2D4ECC0300B8F49A /* HomepageUITests */, 356 | ); 357 | }; 358 | /* End PBXProject section */ 359 | 360 | /* Begin PBXResourcesBuildPhase section */ 361 | 9C93CDAA2D4ECC0300B8F49A /* Resources */ = { 362 | isa = PBXResourcesBuildPhase; 363 | buildActionMask = 2147483647; 364 | files = ( 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | 9C99557C2C18C02100EB9E17 /* Resources */ = { 369 | isa = PBXResourcesBuildPhase; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | 9CBD9AF52C90BB6B00F9EE29 /* Resources */ = { 376 | isa = PBXResourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | ); 380 | runOnlyForDeploymentPostprocessing = 0; 381 | }; 382 | 9CC4B4F22BBEF5B4007BC68B /* Resources */ = { 383 | isa = PBXResourcesBuildPhase; 384 | buildActionMask = 2147483647; 385 | files = ( 386 | ); 387 | runOnlyForDeploymentPostprocessing = 0; 388 | }; 389 | 9CEA5B212BBF3FE500ACF6DC /* Resources */ = { 390 | isa = PBXResourcesBuildPhase; 391 | buildActionMask = 2147483647; 392 | files = ( 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | }; 396 | /* End PBXResourcesBuildPhase section */ 397 | 398 | /* Begin PBXShellScriptBuildPhase section */ 399 | 9C9955792C18BD7100EB9E17 /* SwiftLint */ = { 400 | isa = PBXShellScriptBuildPhase; 401 | alwaysOutOfDate = 1; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | ); 405 | inputFileListPaths = ( 406 | ); 407 | inputPaths = ( 408 | ); 409 | name = SwiftLint; 410 | outputFileListPaths = ( 411 | ); 412 | outputPaths = ( 413 | ); 414 | runOnlyForDeploymentPostprocessing = 0; 415 | shellPath = /bin/sh; 416 | shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 417 | }; 418 | /* End PBXShellScriptBuildPhase section */ 419 | 420 | /* Begin PBXSourcesBuildPhase section */ 421 | 9C93CDA82D4ECC0300B8F49A /* Sources */ = { 422 | isa = PBXSourcesBuildPhase; 423 | buildActionMask = 2147483647; 424 | files = ( 425 | ); 426 | runOnlyForDeploymentPostprocessing = 0; 427 | }; 428 | 9C99557A2C18C02100EB9E17 /* Sources */ = { 429 | isa = PBXSourcesBuildPhase; 430 | buildActionMask = 2147483647; 431 | files = ( 432 | ); 433 | runOnlyForDeploymentPostprocessing = 0; 434 | }; 435 | 9CBD9AF32C90BB6B00F9EE29 /* Sources */ = { 436 | isa = PBXSourcesBuildPhase; 437 | buildActionMask = 2147483647; 438 | files = ( 439 | ); 440 | runOnlyForDeploymentPostprocessing = 0; 441 | }; 442 | 9CC4B4F02BBEF5B4007BC68B /* Sources */ = { 443 | isa = PBXSourcesBuildPhase; 444 | buildActionMask = 2147483647; 445 | files = ( 446 | ); 447 | runOnlyForDeploymentPostprocessing = 0; 448 | }; 449 | 9CEA5B1F2BBF3FE500ACF6DC /* Sources */ = { 450 | isa = PBXSourcesBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | ); 454 | runOnlyForDeploymentPostprocessing = 0; 455 | }; 456 | /* End PBXSourcesBuildPhase section */ 457 | 458 | /* Begin PBXTargetDependency section */ 459 | 9C93CDB32D4ECC0300B8F49A /* PBXTargetDependency */ = { 460 | isa = PBXTargetDependency; 461 | target = 9CC4B4F32BBEF5B4007BC68B /* Homepage */; 462 | targetProxy = 9C93CDB22D4ECC0300B8F49A /* PBXContainerItemProxy */; 463 | }; 464 | 9C9955832C18C02100EB9E17 /* PBXTargetDependency */ = { 465 | isa = PBXTargetDependency; 466 | target = 9CC4B4F32BBEF5B4007BC68B /* Homepage */; 467 | targetProxy = 9C9955822C18C02100EB9E17 /* PBXContainerItemProxy */; 468 | }; 469 | 9CBD9B082C90BB6C00F9EE29 /* PBXTargetDependency */ = { 470 | isa = PBXTargetDependency; 471 | target = 9CBD9AF62C90BB6B00F9EE29 /* HomepageWidgetExtension */; 472 | targetProxy = 9CBD9B072C90BB6C00F9EE29 /* PBXContainerItemProxy */; 473 | }; 474 | 9CEA5B3A2BBF3FE500ACF6DC /* PBXTargetDependency */ = { 475 | isa = PBXTargetDependency; 476 | target = 9CEA5B222BBF3FE500ACF6DC /* HomepageExtension */; 477 | targetProxy = 9CEA5B392BBF3FE500ACF6DC /* PBXContainerItemProxy */; 478 | }; 479 | /* End PBXTargetDependency section */ 480 | 481 | /* Begin XCBuildConfiguration section */ 482 | 9C93CDB42D4ECC0300B8F49A /* Debug */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | CODE_SIGN_STYLE = Automatic; 486 | CURRENT_PROJECT_VERSION = 1; 487 | DEVELOPMENT_TEAM = 3W9UA5X43W; 488 | GENERATE_INFOPLIST_FILE = YES; 489 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 490 | MARKETING_VERSION = 1.0; 491 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.HomepageUITests; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 494 | SUPPORTS_MACCATALYST = NO; 495 | SWIFT_EMIT_LOC_STRINGS = NO; 496 | SWIFT_VERSION = 5.0; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | TEST_TARGET_NAME = Homepage; 499 | }; 500 | name = Debug; 501 | }; 502 | 9C93CDB52D4ECC0300B8F49A /* Release */ = { 503 | isa = XCBuildConfiguration; 504 | buildSettings = { 505 | CODE_SIGN_STYLE = Automatic; 506 | CURRENT_PROJECT_VERSION = 1; 507 | DEVELOPMENT_TEAM = 3W9UA5X43W; 508 | GENERATE_INFOPLIST_FILE = YES; 509 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 510 | MARKETING_VERSION = 1.0; 511 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.HomepageUITests; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 514 | SUPPORTS_MACCATALYST = NO; 515 | SWIFT_EMIT_LOC_STRINGS = NO; 516 | SWIFT_VERSION = 5.0; 517 | TARGETED_DEVICE_FAMILY = "1,2"; 518 | TEST_TARGET_NAME = Homepage; 519 | }; 520 | name = Release; 521 | }; 522 | 9C9955852C18C02100EB9E17 /* Debug */ = { 523 | isa = XCBuildConfiguration; 524 | buildSettings = { 525 | BUNDLE_LOADER = "$(TEST_HOST)"; 526 | CODE_SIGN_STYLE = Automatic; 527 | CURRENT_PROJECT_VERSION = 1; 528 | DEVELOPMENT_TEAM = 3W9UA5X43W; 529 | GENERATE_INFOPLIST_FILE = YES; 530 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 531 | MARKETING_VERSION = 1.0; 532 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.HomepageTests; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 535 | SUPPORTS_MACCATALYST = NO; 536 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 537 | SWIFT_EMIT_LOC_STRINGS = NO; 538 | SWIFT_VERSION = 6.0; 539 | TARGETED_DEVICE_FAMILY = "1,2"; 540 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Homepage.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Homepage"; 541 | }; 542 | name = Debug; 543 | }; 544 | 9C9955862C18C02100EB9E17 /* Release */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | BUNDLE_LOADER = "$(TEST_HOST)"; 548 | CODE_SIGN_STYLE = Automatic; 549 | CURRENT_PROJECT_VERSION = 1; 550 | DEVELOPMENT_TEAM = 3W9UA5X43W; 551 | GENERATE_INFOPLIST_FILE = YES; 552 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 553 | MARKETING_VERSION = 1.0; 554 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.HomepageTests; 555 | PRODUCT_NAME = "$(TARGET_NAME)"; 556 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 557 | SUPPORTS_MACCATALYST = NO; 558 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 559 | SWIFT_EMIT_LOC_STRINGS = NO; 560 | SWIFT_VERSION = 6.0; 561 | TARGETED_DEVICE_FAMILY = "1,2"; 562 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Homepage.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Homepage"; 563 | }; 564 | name = Release; 565 | }; 566 | 9CBD9B0A2C90BB6C00F9EE29 /* Debug */ = { 567 | isa = XCBuildConfiguration; 568 | buildSettings = { 569 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 570 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; 571 | CODE_SIGN_STYLE = Automatic; 572 | CURRENT_PROJECT_VERSION = 1; 573 | DEVELOPMENT_TEAM = 3W9UA5X43W; 574 | GENERATE_INFOPLIST_FILE = YES; 575 | INFOPLIST_FILE = HomepageWidget/Info.plist; 576 | INFOPLIST_KEY_CFBundleDisplayName = HomepageWidget; 577 | INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Ahnaf Mahmud"; 578 | IPHONEOS_DEPLOYMENT_TARGET = 18.0; 579 | LD_RUNPATH_SEARCH_PATHS = ( 580 | "$(inherited)", 581 | "@executable_path/Frameworks", 582 | "@executable_path/../../Frameworks", 583 | ); 584 | MARKETING_VERSION = 1.0; 585 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.Homepage.HomepageWidget; 586 | PRODUCT_NAME = "$(TARGET_NAME)"; 587 | SKIP_INSTALL = YES; 588 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 589 | SUPPORTS_MACCATALYST = NO; 590 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 591 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; 592 | SWIFT_EMIT_LOC_STRINGS = YES; 593 | SWIFT_VERSION = 6.0; 594 | TARGETED_DEVICE_FAMILY = "1,2"; 595 | }; 596 | name = Debug; 597 | }; 598 | 9CBD9B0B2C90BB6C00F9EE29 /* Release */ = { 599 | isa = XCBuildConfiguration; 600 | buildSettings = { 601 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 602 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; 603 | CODE_SIGN_STYLE = Automatic; 604 | CURRENT_PROJECT_VERSION = 1; 605 | DEVELOPMENT_TEAM = 3W9UA5X43W; 606 | GENERATE_INFOPLIST_FILE = YES; 607 | INFOPLIST_FILE = HomepageWidget/Info.plist; 608 | INFOPLIST_KEY_CFBundleDisplayName = HomepageWidget; 609 | INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Ahnaf Mahmud"; 610 | IPHONEOS_DEPLOYMENT_TARGET = 18.0; 611 | LD_RUNPATH_SEARCH_PATHS = ( 612 | "$(inherited)", 613 | "@executable_path/Frameworks", 614 | "@executable_path/../../Frameworks", 615 | ); 616 | MARKETING_VERSION = 1.0; 617 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.Homepage.HomepageWidget; 618 | PRODUCT_NAME = "$(TARGET_NAME)"; 619 | SKIP_INSTALL = YES; 620 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 621 | SUPPORTS_MACCATALYST = NO; 622 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 623 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; 624 | SWIFT_EMIT_LOC_STRINGS = YES; 625 | SWIFT_VERSION = 6.0; 626 | TARGETED_DEVICE_FAMILY = "1,2"; 627 | }; 628 | name = Release; 629 | }; 630 | 9CC4B5002BBEF5B5007BC68B /* Debug */ = { 631 | isa = XCBuildConfiguration; 632 | buildSettings = { 633 | ALWAYS_SEARCH_USER_PATHS = NO; 634 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 635 | CLANG_ANALYZER_NONNULL = YES; 636 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 637 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 638 | CLANG_ENABLE_MODULES = YES; 639 | CLANG_ENABLE_OBJC_ARC = YES; 640 | CLANG_ENABLE_OBJC_WEAK = YES; 641 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 642 | CLANG_WARN_BOOL_CONVERSION = YES; 643 | CLANG_WARN_COMMA = YES; 644 | CLANG_WARN_CONSTANT_CONVERSION = YES; 645 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 646 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 647 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 648 | CLANG_WARN_EMPTY_BODY = YES; 649 | CLANG_WARN_ENUM_CONVERSION = YES; 650 | CLANG_WARN_INFINITE_RECURSION = YES; 651 | CLANG_WARN_INT_CONVERSION = YES; 652 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 653 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 654 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 655 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 656 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 657 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 658 | CLANG_WARN_STRICT_PROTOTYPES = YES; 659 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 660 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 661 | CLANG_WARN_UNREACHABLE_CODE = YES; 662 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 663 | COPY_PHASE_STRIP = NO; 664 | DEBUG_INFORMATION_FORMAT = dwarf; 665 | ENABLE_STRICT_OBJC_MSGSEND = YES; 666 | ENABLE_TESTABILITY = YES; 667 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 668 | GCC_C_LANGUAGE_STANDARD = gnu17; 669 | GCC_DYNAMIC_NO_PIC = NO; 670 | GCC_NO_COMMON_BLOCKS = YES; 671 | GCC_OPTIMIZATION_LEVEL = 0; 672 | GCC_PREPROCESSOR_DEFINITIONS = ( 673 | "DEBUG=1", 674 | "$(inherited)", 675 | ); 676 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 677 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 678 | GCC_WARN_UNDECLARED_SELECTOR = YES; 679 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 680 | GCC_WARN_UNUSED_FUNCTION = YES; 681 | GCC_WARN_UNUSED_VARIABLE = YES; 682 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 683 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 684 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 685 | MTL_FAST_MATH = YES; 686 | ONLY_ACTIVE_ARCH = YES; 687 | SDKROOT = iphoneos; 688 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 689 | SWIFT_EMIT_LOC_STRINGS = YES; 690 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 691 | }; 692 | name = Debug; 693 | }; 694 | 9CC4B5012BBEF5B5007BC68B /* Release */ = { 695 | isa = XCBuildConfiguration; 696 | buildSettings = { 697 | ALWAYS_SEARCH_USER_PATHS = NO; 698 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 699 | CLANG_ANALYZER_NONNULL = YES; 700 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 701 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 702 | CLANG_ENABLE_MODULES = YES; 703 | CLANG_ENABLE_OBJC_ARC = YES; 704 | CLANG_ENABLE_OBJC_WEAK = YES; 705 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 706 | CLANG_WARN_BOOL_CONVERSION = YES; 707 | CLANG_WARN_COMMA = YES; 708 | CLANG_WARN_CONSTANT_CONVERSION = YES; 709 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 710 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 711 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 712 | CLANG_WARN_EMPTY_BODY = YES; 713 | CLANG_WARN_ENUM_CONVERSION = YES; 714 | CLANG_WARN_INFINITE_RECURSION = YES; 715 | CLANG_WARN_INT_CONVERSION = YES; 716 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 717 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 718 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 719 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 720 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 721 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 722 | CLANG_WARN_STRICT_PROTOTYPES = YES; 723 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 724 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 725 | CLANG_WARN_UNREACHABLE_CODE = YES; 726 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 727 | COPY_PHASE_STRIP = NO; 728 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 729 | ENABLE_NS_ASSERTIONS = NO; 730 | ENABLE_STRICT_OBJC_MSGSEND = YES; 731 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 732 | GCC_C_LANGUAGE_STANDARD = gnu17; 733 | GCC_NO_COMMON_BLOCKS = YES; 734 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 735 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 736 | GCC_WARN_UNDECLARED_SELECTOR = YES; 737 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 738 | GCC_WARN_UNUSED_FUNCTION = YES; 739 | GCC_WARN_UNUSED_VARIABLE = YES; 740 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 741 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 742 | MTL_ENABLE_DEBUG_INFO = NO; 743 | MTL_FAST_MATH = YES; 744 | SDKROOT = iphoneos; 745 | SWIFT_COMPILATION_MODE = wholemodule; 746 | SWIFT_EMIT_LOC_STRINGS = YES; 747 | VALIDATE_PRODUCT = YES; 748 | }; 749 | name = Release; 750 | }; 751 | 9CC4B5032BBEF5B5007BC68B /* Debug */ = { 752 | isa = XCBuildConfiguration; 753 | buildSettings = { 754 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 755 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 756 | CODE_SIGN_ENTITLEMENTS = Homepage/Homepage.entitlements; 757 | CODE_SIGN_STYLE = Automatic; 758 | CURRENT_PROJECT_VERSION = 33; 759 | DEVELOPMENT_ASSET_PATHS = "\"Homepage/Preview Content\""; 760 | DEVELOPMENT_TEAM = 3W9UA5X43W; 761 | ENABLE_PREVIEWS = YES; 762 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 763 | GENERATE_INFOPLIST_FILE = YES; 764 | INFOPLIST_FILE = Homepage/Info.plist; 765 | INFOPLIST_KEY_CFBundleDisplayName = Homepage; 766 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 767 | INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Ahnaf Mahmud"; 768 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 769 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 770 | INFOPLIST_KEY_UIRequiresFullScreen = YES; 771 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 772 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 773 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 774 | LD_RUNPATH_SEARCH_PATHS = ( 775 | "$(inherited)", 776 | "@executable_path/Frameworks", 777 | ); 778 | MARKETING_VERSION = 2.1.5; 779 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.Homepage; 780 | PRODUCT_NAME = "$(TARGET_NAME)"; 781 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 782 | SUPPORTS_MACCATALYST = NO; 783 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 784 | SWIFT_EMIT_LOC_STRINGS = YES; 785 | SWIFT_STRICT_CONCURRENCY = complete; 786 | SWIFT_VERSION = 6.0; 787 | TARGETED_DEVICE_FAMILY = "1,2"; 788 | XROS_DEPLOYMENT_TARGET = 1.0; 789 | }; 790 | name = Debug; 791 | }; 792 | 9CC4B5042BBEF5B5007BC68B /* Release */ = { 793 | isa = XCBuildConfiguration; 794 | buildSettings = { 795 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 796 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 797 | CODE_SIGN_ENTITLEMENTS = Homepage/Homepage.entitlements; 798 | CODE_SIGN_STYLE = Automatic; 799 | CURRENT_PROJECT_VERSION = 33; 800 | DEVELOPMENT_ASSET_PATHS = "\"Homepage/Preview Content\""; 801 | DEVELOPMENT_TEAM = 3W9UA5X43W; 802 | ENABLE_PREVIEWS = YES; 803 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 804 | GENERATE_INFOPLIST_FILE = YES; 805 | INFOPLIST_FILE = Homepage/Info.plist; 806 | INFOPLIST_KEY_CFBundleDisplayName = Homepage; 807 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 808 | INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Ahnaf Mahmud"; 809 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 810 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 811 | INFOPLIST_KEY_UIRequiresFullScreen = YES; 812 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 813 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 814 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 815 | LD_RUNPATH_SEARCH_PATHS = ( 816 | "$(inherited)", 817 | "@executable_path/Frameworks", 818 | ); 819 | MARKETING_VERSION = 2.1.5; 820 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.Homepage; 821 | PRODUCT_NAME = "$(TARGET_NAME)"; 822 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 823 | SUPPORTS_MACCATALYST = NO; 824 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 825 | SWIFT_EMIT_LOC_STRINGS = YES; 826 | SWIFT_STRICT_CONCURRENCY = complete; 827 | SWIFT_VERSION = 6.0; 828 | TARGETED_DEVICE_FAMILY = "1,2"; 829 | XROS_DEPLOYMENT_TARGET = 1.0; 830 | }; 831 | name = Release; 832 | }; 833 | 9CEA5B3D2BBF3FE500ACF6DC /* Debug */ = { 834 | isa = XCBuildConfiguration; 835 | buildSettings = { 836 | CODE_SIGN_ENTITLEMENTS = HomepageExtension/HomepageExtension.entitlements; 837 | CODE_SIGN_STYLE = Automatic; 838 | CURRENT_PROJECT_VERSION = 1; 839 | DEVELOPMENT_TEAM = 3W9UA5X43W; 840 | GENERATE_INFOPLIST_FILE = YES; 841 | INFOPLIST_FILE = HomepageExtension/Info.plist; 842 | INFOPLIST_KEY_CFBundleDisplayName = HomepageExtension; 843 | INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Ahnaf Mahmud"; 844 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 845 | LD_RUNPATH_SEARCH_PATHS = ( 846 | "$(inherited)", 847 | "@executable_path/Frameworks", 848 | "@executable_path/../../Frameworks", 849 | ); 850 | MARKETING_VERSION = 1.0; 851 | OTHER_LDFLAGS = ( 852 | "-framework", 853 | SafariServices, 854 | ); 855 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.Homepage.HomepageExtension; 856 | PRODUCT_NAME = "$(TARGET_NAME)"; 857 | SKIP_INSTALL = YES; 858 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 859 | SUPPORTS_MACCATALYST = NO; 860 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 861 | SWIFT_EMIT_LOC_STRINGS = YES; 862 | SWIFT_VERSION = 6.0; 863 | TARGETED_DEVICE_FAMILY = "1,2"; 864 | XROS_DEPLOYMENT_TARGET = 1.0; 865 | }; 866 | name = Debug; 867 | }; 868 | 9CEA5B3E2BBF3FE500ACF6DC /* Release */ = { 869 | isa = XCBuildConfiguration; 870 | buildSettings = { 871 | CODE_SIGN_ENTITLEMENTS = HomepageExtension/HomepageExtension.entitlements; 872 | CODE_SIGN_STYLE = Automatic; 873 | CURRENT_PROJECT_VERSION = 1; 874 | DEVELOPMENT_TEAM = 3W9UA5X43W; 875 | GENERATE_INFOPLIST_FILE = YES; 876 | INFOPLIST_FILE = HomepageExtension/Info.plist; 877 | INFOPLIST_KEY_CFBundleDisplayName = HomepageExtension; 878 | INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Ahnaf Mahmud"; 879 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 880 | LD_RUNPATH_SEARCH_PATHS = ( 881 | "$(inherited)", 882 | "@executable_path/Frameworks", 883 | "@executable_path/../../Frameworks", 884 | ); 885 | MARKETING_VERSION = 1.0; 886 | OTHER_LDFLAGS = ( 887 | "-framework", 888 | SafariServices, 889 | ); 890 | PRODUCT_BUNDLE_IDENTIFIER = com.ip18.Homepage.HomepageExtension; 891 | PRODUCT_NAME = "$(TARGET_NAME)"; 892 | SKIP_INSTALL = YES; 893 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 894 | SUPPORTS_MACCATALYST = NO; 895 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 896 | SWIFT_EMIT_LOC_STRINGS = YES; 897 | SWIFT_VERSION = 6.0; 898 | TARGETED_DEVICE_FAMILY = "1,2"; 899 | XROS_DEPLOYMENT_TARGET = 1.0; 900 | }; 901 | name = Release; 902 | }; 903 | /* End XCBuildConfiguration section */ 904 | 905 | /* Begin XCConfigurationList section */ 906 | 9C93CDB62D4ECC0300B8F49A /* Build configuration list for PBXNativeTarget "HomepageUITests" */ = { 907 | isa = XCConfigurationList; 908 | buildConfigurations = ( 909 | 9C93CDB42D4ECC0300B8F49A /* Debug */, 910 | 9C93CDB52D4ECC0300B8F49A /* Release */, 911 | ); 912 | defaultConfigurationIsVisible = 0; 913 | defaultConfigurationName = Release; 914 | }; 915 | 9C9955842C18C02100EB9E17 /* Build configuration list for PBXNativeTarget "HomepageTests" */ = { 916 | isa = XCConfigurationList; 917 | buildConfigurations = ( 918 | 9C9955852C18C02100EB9E17 /* Debug */, 919 | 9C9955862C18C02100EB9E17 /* Release */, 920 | ); 921 | defaultConfigurationIsVisible = 0; 922 | defaultConfigurationName = Release; 923 | }; 924 | 9CBD9B0D2C90BB6C00F9EE29 /* Build configuration list for PBXNativeTarget "HomepageWidgetExtension" */ = { 925 | isa = XCConfigurationList; 926 | buildConfigurations = ( 927 | 9CBD9B0A2C90BB6C00F9EE29 /* Debug */, 928 | 9CBD9B0B2C90BB6C00F9EE29 /* Release */, 929 | ); 930 | defaultConfigurationIsVisible = 0; 931 | defaultConfigurationName = Release; 932 | }; 933 | 9CC4B4EF2BBEF5B4007BC68B /* Build configuration list for PBXProject "Homepage" */ = { 934 | isa = XCConfigurationList; 935 | buildConfigurations = ( 936 | 9CC4B5002BBEF5B5007BC68B /* Debug */, 937 | 9CC4B5012BBEF5B5007BC68B /* Release */, 938 | ); 939 | defaultConfigurationIsVisible = 0; 940 | defaultConfigurationName = Release; 941 | }; 942 | 9CC4B5022BBEF5B5007BC68B /* Build configuration list for PBXNativeTarget "Homepage" */ = { 943 | isa = XCConfigurationList; 944 | buildConfigurations = ( 945 | 9CC4B5032BBEF5B5007BC68B /* Debug */, 946 | 9CC4B5042BBEF5B5007BC68B /* Release */, 947 | ); 948 | defaultConfigurationIsVisible = 0; 949 | defaultConfigurationName = Release; 950 | }; 951 | 9CEA5B3C2BBF3FE500ACF6DC /* Build configuration list for PBXNativeTarget "HomepageExtension" */ = { 952 | isa = XCConfigurationList; 953 | buildConfigurations = ( 954 | 9CEA5B3D2BBF3FE500ACF6DC /* Debug */, 955 | 9CEA5B3E2BBF3FE500ACF6DC /* Release */, 956 | ); 957 | defaultConfigurationIsVisible = 0; 958 | defaultConfigurationName = Release; 959 | }; 960 | /* End XCConfigurationList section */ 961 | }; 962 | rootObject = 9CC4B4EC2BBEF5B4007BC68B /* Project object */; 963 | } 964 | -------------------------------------------------------------------------------- /Homepage.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Homepage.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Homepage/App Intents/OpenAppIntent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenAppIntent.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 10/09/2024. 6 | // 7 | 8 | import AppIntents 9 | 10 | /// An app intent to open the app 11 | @available(iOS 16.0, *) 12 | struct OpenAppIntent: AppIntent { 13 | 14 | static let title: LocalizedStringResource = "openApp" 15 | static let openAppWhenRun: Bool = true 16 | 17 | func perform() async throws -> some IntentResult { 18 | return .result() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Homepage/App Intents/SetHomepageIntent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetHomepageIntent.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 29/08/2024. 6 | // 7 | 8 | import AppIntents 9 | 10 | /// An app intent to set a new homepage URL 11 | @available(iOS 16.0, *) 12 | struct SetHomepageIntent: AppIntent { 13 | 14 | static let title: LocalizedStringResource = "setHomepage" 15 | static let description: LocalizedStringResource = "setNewHomepage" 16 | 17 | @Parameter(title: "url") 18 | var homepageURL: URL 19 | 20 | func perform() async throws -> some IntentResult & ProvidesDialog { 21 | let urlString = homepageURL.absoluteString 22 | if URLValidator.isValidURL(urlString) { 23 | UserDefaults.homepage = urlString 24 | return .result(dialog: IntentDialog("homepageSavedDescription")) 25 | } else { 26 | return .result(dialog: IntentDialog("validationError")) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Homepage/App Intents/ShortcutsProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutsProvider.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 29/08/2024. 6 | // 7 | 8 | import AppIntents 9 | 10 | @available(iOS 16.0, *) 11 | struct ShortcutsProvider: AppShortcutsProvider { 12 | 13 | static var appShortcuts: [AppShortcut] { 14 | AppShortcut( 15 | intent: SetHomepageIntent(), 16 | phrases: [ 17 | "Set a new \(.applicationName)", 18 | "Change \(.applicationName) URL", 19 | "Change my \(.applicationName)" 20 | ], 21 | shortTitle: "setHomepage", 22 | systemImageName: "house.fill" 23 | ) 24 | } 25 | 26 | static let shortcutTileColor: ShortcutTileColor = .purple 27 | } 28 | -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0x71", 10 | "red" : "0x52" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppIcon.appiconset/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/Homepage/Assets.xcassets/AppIcon.appiconset/AppIcon.png -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ], 16 | "filename" : "DarkIcon.png", 17 | "idiom" : "universal", 18 | "platform" : "ios", 19 | "size" : "1024x1024" 20 | }, 21 | { 22 | "appearances" : [ 23 | { 24 | "appearance" : "luminosity", 25 | "value" : "tinted" 26 | } 27 | ], 28 | "filename" : "TintedIcon.png", 29 | "idiom" : "universal", 30 | "platform" : "ios", 31 | "size" : "1024x1024" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppIcon.appiconset/DarkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/Homepage/Assets.xcassets/AppIcon.appiconset/DarkIcon.png -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppIcon.appiconset/TintedIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/Homepage/Assets.xcassets/AppIcon.appiconset/TintedIcon.png -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppImage.imageset/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/Homepage/Assets.xcassets/AppImage.imageset/AppIcon.png -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "DarkIcon.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/AppImage.imageset/DarkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/Homepage/Assets.xcassets/AppImage.imageset/DarkIcon.png -------------------------------------------------------------------------------- /Homepage/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Homepage/Extensions/Bundle+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Extension.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 15/06/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bundle { 11 | /// Get the icon file name 12 | static var iconFileName: String? { 13 | guard 14 | let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any], 15 | let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], 16 | let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], 17 | let iconFileName = iconFiles.last 18 | else { 19 | #if DEBUG 20 | print("Could not find icons in bundle") 21 | #endif 22 | return nil 23 | } 24 | return iconFileName 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Homepage/Extensions/UIApplication+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Extension.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 19/11/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIApplication { 11 | /// The application version 12 | static var appVersion: String? { 13 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Homepage/Helpers/URLValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLValidator.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 11/06/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class URLValidator { 11 | 12 | /// Checks if the URL is a complete URL 13 | /// - Parameter urlString: The URL to check 14 | /// - Returns: Whether the URL is complete 15 | static func isCompleteURL(_ urlString: String) -> Bool { 16 | if urlString == "about:blank" { 17 | return true 18 | } 19 | 20 | if urlString.starts(with: "http://") || urlString.starts(with: "https://") || urlString.starts(with: "data:") { 21 | return true 22 | } 23 | 24 | return false 25 | } 26 | 27 | /// Checks if the URL is valid 28 | /// - Parameter urlString: The URL to validate 29 | /// - Returns: Whether the URL is valid 30 | static func isValidURL(_ urlString: String) -> Bool { 31 | // Check if URL is "about:blank" 32 | if urlString == "about:blank" { 33 | return true 34 | } 35 | 36 | if urlString.starts(with: "data:") { 37 | return true 38 | } 39 | 40 | // swiftlint:disable line_length 41 | let urlRegex = #"^(https?:\/\/)(([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}|((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))(:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[0-9]{1,4}))?(\/[^\s]*)?$"# // Regular expression for URL validation 42 | // swiftlint:enable line_length 43 | 44 | // Check if the URL matches the regex 45 | if urlString.range(of: urlRegex, options: .regularExpression) != nil { 46 | return true 47 | } 48 | 49 | return false 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Homepage/Homepage.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.ip18.Homepage 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Homepage/HomepageApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomepageApp.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 04/04/2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct HomepageApp: App { 12 | 13 | init() { 14 | let isUiTest = ProcessInfo.processInfo.arguments.contains("ui-testing") 15 | if UserDefaults.clearUrl || isUiTest { 16 | UserDefaults.homepage = nil 17 | UserDefaults.clearUrl = false 18 | } 19 | UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = UIColor.accent 20 | } 21 | 22 | var body: some Scene { 23 | WindowGroup { 24 | ContentView() 25 | .tint(.accent) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Homepage/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIcons 6 | 7 | CFBundlePrimaryIcon 8 | 9 | NSAppIconComplementingColorNames 10 | 11 | AccentColor 12 | 13 | 14 | 15 | CFBundleIcons~ipad 16 | 17 | CFBundlePrimaryIcon 18 | 19 | NSAppIconComplementingColorNames 20 | 21 | AccentColor 22 | 23 | 24 | 25 | CFBundleURLTypes 26 | 27 | 28 | CFBundleTypeRole 29 | Viewer 30 | CFBundleURLName 31 | com.ip18.Homepage 32 | CFBundleURLSchemes 33 | 34 | homepage 35 | 36 | 37 | 38 | ITSAppUsesNonExemptEncryption 39 | 40 | UIApplicationSceneManifest 41 | 42 | UIApplicationSupportsMultipleScenes 43 | 44 | UISceneConfigurations 45 | 46 | 47 | UIPreferredDefaultInterfaceOrientation 48 | UIInterfaceOrientationLandscapeLeft 49 | 50 | 51 | -------------------------------------------------------------------------------- /Homepage/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Homepage/Resources/Localizable.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "aboutButton" : { 5 | "extractionState" : "manual", 6 | "localizations" : { 7 | "en" : { 8 | "stringUnit" : { 9 | "state" : "translated", 10 | "value" : "About" 11 | } 12 | } 13 | } 14 | }, 15 | "appName" : { 16 | "extractionState" : "manual", 17 | "localizations" : { 18 | "en" : { 19 | "stringUnit" : { 20 | "state" : "translated", 21 | "value" : "Homepage for Safari" 22 | } 23 | } 24 | } 25 | }, 26 | "close" : { 27 | "extractionState" : "manual", 28 | "localizations" : { 29 | "en" : { 30 | "stringUnit" : { 31 | "state" : "translated", 32 | "value" : "Close" 33 | } 34 | } 35 | } 36 | }, 37 | "copyright" : { 38 | "extractionState" : "manual", 39 | "localizations" : { 40 | "en" : { 41 | "stringUnit" : { 42 | "state" : "translated", 43 | "value" : "© 2025 Ahnaf Mahmud" 44 | } 45 | } 46 | } 47 | }, 48 | "done" : { 49 | "extractionState" : "manual", 50 | "localizations" : { 51 | "en" : { 52 | "stringUnit" : { 53 | "state" : "translated", 54 | "value" : "Done" 55 | } 56 | } 57 | } 58 | }, 59 | "homepageIcon" : { 60 | "extractionState" : "manual", 61 | "localizations" : { 62 | "en" : { 63 | "stringUnit" : { 64 | "state" : "translated", 65 | "value" : "Homepage for Safari icon" 66 | } 67 | } 68 | } 69 | }, 70 | "homepageSaved" : { 71 | "extractionState" : "manual", 72 | "localizations" : { 73 | "en" : { 74 | "stringUnit" : { 75 | "state" : "translated", 76 | "value" : "Homepage Saved" 77 | } 78 | } 79 | } 80 | }, 81 | "homepageSavedDescription" : { 82 | "extractionState" : "manual", 83 | "localizations" : { 84 | "en" : { 85 | "stringUnit" : { 86 | "state" : "translated", 87 | "value" : "Your homepage has been saved and will be loaded the next time you open a new tab." 88 | } 89 | } 90 | } 91 | }, 92 | "invalidURL" : { 93 | "extractionState" : "manual", 94 | "localizations" : { 95 | "en" : { 96 | "stringUnit" : { 97 | "state" : "translated", 98 | "value" : "Invalid URL" 99 | } 100 | } 101 | } 102 | }, 103 | "ok" : { 104 | "extractionState" : "manual", 105 | "localizations" : { 106 | "en" : { 107 | "stringUnit" : { 108 | "state" : "translated", 109 | "value" : "OK" 110 | } 111 | } 112 | } 113 | }, 114 | "openApp" : { 115 | "extractionState" : "manual", 116 | "localizations" : { 117 | "en" : { 118 | "stringUnit" : { 119 | "state" : "translated", 120 | "value" : "Open App" 121 | } 122 | } 123 | } 124 | }, 125 | "save" : { 126 | "extractionState" : "manual", 127 | "localizations" : { 128 | "en" : { 129 | "stringUnit" : { 130 | "state" : "translated", 131 | "value" : "Save" 132 | } 133 | } 134 | } 135 | }, 136 | "setHomepage" : { 137 | "extractionState" : "manual", 138 | "localizations" : { 139 | "en" : { 140 | "stringUnit" : { 141 | "state" : "translated", 142 | "value" : "Set Homepage" 143 | } 144 | } 145 | } 146 | }, 147 | "setNewHomepage" : { 148 | "extractionState" : "manual", 149 | "localizations" : { 150 | "en" : { 151 | "stringUnit" : { 152 | "state" : "translated", 153 | "value" : "Set a new homepage" 154 | } 155 | } 156 | } 157 | }, 158 | "setupHelp" : { 159 | "extractionState" : "manual", 160 | "localizations" : { 161 | "en" : { 162 | "stringUnit" : { 163 | "state" : "translated", 164 | "value" : "To load your homepage whenever a new tab is opened, ensure the extension is enabled and that this app is set under Open New Tabs in Safari Settings." 165 | } 166 | } 167 | } 168 | }, 169 | "support" : { 170 | "extractionState" : "manual", 171 | "localizations" : { 172 | "en" : { 173 | "stringUnit" : { 174 | "state" : "translated", 175 | "value" : "Support" 176 | } 177 | } 178 | } 179 | }, 180 | "title" : { 181 | "extractionState" : "manual", 182 | "localizations" : { 183 | "en" : { 184 | "stringUnit" : { 185 | "state" : "translated", 186 | "value" : "Homepage Safari Extension" 187 | } 188 | } 189 | } 190 | }, 191 | "url" : { 192 | "extractionState" : "manual", 193 | "localizations" : { 194 | "en" : { 195 | "stringUnit" : { 196 | "state" : "translated", 197 | "value" : "Homepage URL" 198 | } 199 | } 200 | } 201 | }, 202 | "validationError" : { 203 | "extractionState" : "manual", 204 | "localizations" : { 205 | "en" : { 206 | "stringUnit" : { 207 | "state" : "translated", 208 | "value" : "The URL entered is invalid. Please check that it is in a valid format." 209 | } 210 | } 211 | } 212 | }, 213 | "version %@" : { 214 | "extractionState" : "manual", 215 | "localizations" : { 216 | "en" : { 217 | "stringUnit" : { 218 | "state" : "translated", 219 | "value" : "Version %@" 220 | } 221 | } 222 | } 223 | } 224 | }, 225 | "version" : "1.0" 226 | } -------------------------------------------------------------------------------- /Homepage/Settings.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | FooterText 13 | URL will be cleared on next app launch 14 | 15 | 16 | Type 17 | PSToggleSwitchSpecifier 18 | Title 19 | Clear Homepage URL 20 | Key 21 | clear_url 22 | DefaultValue 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Homepage/Settings.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/Homepage/Settings.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /Homepage/View Modifiers/Backport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Backport.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 16/10/2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Backport { 11 | let content: Content 12 | } 13 | 14 | extension View { 15 | var backport: Backport { Backport(content: self) } 16 | } 17 | 18 | extension Backport where Content: View { 19 | @ViewBuilder func bounceBasedOnSize() -> some View { 20 | if #available(iOS 16.4, *) { 21 | content.scrollBounceBehavior(.basedOnSize) 22 | } else { 23 | content 24 | } 25 | } 26 | 27 | @MainActor 28 | @ViewBuilder func disableWritingTools() -> some View { 29 | if #available(iOS 18, *) { 30 | content.writingToolsBehavior(.disabled) 31 | } else { 32 | content 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Homepage/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 04/04/2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @State private var urlInput = "" 13 | @State private var showAlert = false 14 | @State private var alertType: AlertType = .about 15 | 16 | @MainActor private var isPhone: Bool { 17 | UIDevice.current.userInterfaceIdiom == .phone 18 | } 19 | 20 | private let supportUrl = URL(string: "https://ahnafmahmud.com/apps/Homepage/support.html") 21 | 22 | @Environment(\.openURL) private var openURL 23 | 24 | var body: some View { 25 | NavigationView { 26 | GeometryReader { geometry in 27 | ScrollView(.vertical) { 28 | if isPhone { 29 | stack 30 | .frame(width: geometry.size.width) 31 | .frame(minHeight: geometry.size.height) 32 | } else { 33 | HStack { 34 | Spacer() 35 | stack 36 | .frame(width: geometry.size.width / 2) 37 | .frame(minHeight: geometry.size.height) 38 | Spacer() 39 | } 40 | } 41 | } 42 | .backport.bounceBasedOnSize() 43 | } 44 | } 45 | .navigationViewStyle(.stack) 46 | } 47 | 48 | @MainActor private var stack: some View { 49 | VStack(spacing: 20) { 50 | subViews 51 | } 52 | .padding() 53 | .onAppear { 54 | urlInput = UserDefaults.homepage ?? "" 55 | } 56 | .alert(isPresented: $showAlert) { 57 | switch alertType { 58 | case .success: 59 | Alert( 60 | title: Text("homepageSaved"), 61 | message: Text("homepageSavedDescription"), 62 | dismissButton: .default(Text("ok")) 63 | ) 64 | case .failed: 65 | Alert( 66 | title: Text("invalidURL"), 67 | message: Text("validationError"), 68 | dismissButton: .default(Text("ok")) 69 | ) 70 | case .about: 71 | aboutAlert 72 | } 73 | } 74 | .toolbar { 75 | ToolbarItem(placement: .topBarTrailing) { 76 | Button { 77 | alertType = .about 78 | showAlert = true 79 | } label: { 80 | Image(systemName: "info.circle") 81 | .accessibilityLabel("aboutButton") 82 | } 83 | .accessibilityIdentifier("About button") 84 | } 85 | } 86 | } 87 | 88 | private var subViews: some View { 89 | Group { 90 | Image("AppImage") 91 | .resizable() 92 | .frame(width: 64, height: 64) 93 | .cornerRadius(10) 94 | .accessibilityLabel("homepageIcon") 95 | .accessibilityIdentifier("Homepage icon") 96 | Text("title") 97 | .font(.title) 98 | .accessibilityIdentifier("Title") 99 | TextField( 100 | "url", 101 | text: $urlInput 102 | ) 103 | .keyboardType(.URL) 104 | .autocorrectionDisabled(true) 105 | .textInputAutocapitalization(.never) 106 | .textFieldStyle(.roundedBorder) 107 | .accessibilityIdentifier("URL input") 108 | .backport.disableWritingTools() 109 | Button { 110 | if !URLValidator.isCompleteURL(urlInput) { 111 | urlInput = "http://" + urlInput 112 | } 113 | guard URLValidator.isValidURL(urlInput) else { 114 | alertType = .failed 115 | showAlert = true 116 | return 117 | } 118 | UserDefaults.homepage = urlInput 119 | alertType = .success 120 | showAlert = true 121 | } label: { 122 | Text("save") 123 | .font(.title) 124 | } 125 | .accessibilityIdentifier("Save button") 126 | .disabled(urlInput.trimmingCharacters(in: .whitespaces).isEmpty) 127 | Text("setupHelp") 128 | .accessibilityIdentifier("Help text") 129 | } 130 | } 131 | 132 | private var aboutAlert: Alert { 133 | Alert( 134 | title: Text("appName"), 135 | message: Text("version \(UIApplication.appVersion ?? "unknown")") + Text(verbatim: "\n") + Text("copyright"), 136 | primaryButton: .cancel(Text("close")), 137 | secondaryButton: .default(Text("support")) { 138 | if let supportUrl { 139 | openURL(supportUrl) 140 | } 141 | } 142 | ) 143 | } 144 | } 145 | 146 | #Preview { 147 | ContentView() 148 | } 149 | 150 | /// The type of alert to show 151 | enum AlertType { 152 | case success, failed, about 153 | } 154 | -------------------------------------------------------------------------------- /HomepageExtension/HomepageExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.ip18.Homepage 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HomepageExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Homepage", 4 | "description": "The display name for the extension." 5 | }, 6 | "extension_description": { 7 | "message": "Automatically load your specified homepage upon opening a new tab", 8 | "description": "Description of what the extension does." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/background.js: -------------------------------------------------------------------------------- 1 | chrome.action.onClicked.addListener((tab) => { 2 | chrome.runtime.sendNativeMessage("application.id", { message: "get-homepage" }, function(response) { 3 | if (chrome.runtime.lastError) { 4 | console.error(chrome.runtime.lastError); 5 | return; 6 | } 7 | 8 | // Check if response contains the expected "url" key 9 | if (response && response.url) { 10 | chrome.tabs.update({ 11 | url: response.url 12 | }); 13 | } else { 14 | console.error("No URL received in the response."); 15 | } 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Homepage 7 | 8 | 9 | 10 | 11 | 12 |
13 |
You have activated the Homepage extension. Open the app to set a homepage of your choice.
14 |
15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/homepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/homepage.js: -------------------------------------------------------------------------------- 1 | function redirect() { 2 | chrome.runtime.sendNativeMessage("application.id", { message: "get-homepage" }, function(response) { 3 | if (chrome.runtime.lastError) { 4 | console.error(chrome.runtime.lastError); 5 | return; 6 | } 7 | 8 | // Check if response contains the expected "url" key 9 | if (response && response.url) { 10 | window.location.replace(response.url); 11 | } else { 12 | console.error("No URL received in the response."); 13 | } 14 | }); 15 | } 16 | document.addEventListener("DOMContentLoaded", redirect); 17 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/icon-128.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/icon-256.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/icon-48.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/icon-512.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/icon-64.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/icon-96.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/toolbar-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/toolbar-icon-16.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/toolbar-icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/toolbar-icon-19.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/toolbar-icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/toolbar-icon-32.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/toolbar-icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/toolbar-icon-38.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/toolbar-icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/toolbar-icon-48.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/images/toolbar-icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/Homepage-MobileSafari/8c4306333cb367e1268dc806e07b92026c3b742f/HomepageExtension/Resources/images/toolbar-icon-72.png -------------------------------------------------------------------------------- /HomepageExtension/Resources/localize.js: -------------------------------------------------------------------------------- 1 | // Function to load translations 2 | function loadTranslations(lang) { 3 | fetch('translations.json') 4 | .then(response => response.json()) 5 | .then(data => { 6 | document.getElementById('message').textContent = data[lang].message; 7 | document.getElementById('openApp').textContent = data[lang].openApp; 8 | }) 9 | .catch(error => console.error('Error loading translations:', error)); 10 | } 11 | 12 | // Function to get the user's language preference 13 | function getUserLanguage() { 14 | return navigator.language || navigator.userLanguage; 15 | } 16 | 17 | // Load the translations when the page loads 18 | document.addEventListener('DOMContentLoaded', () => { 19 | const userLang = getUserLanguage().slice(0, 2); // Use the first two letters of the language code 20 | loadTranslations(userLang); 21 | }); 22 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "default_locale": "en", 3 | "name": "__MSG_extension_name__", 4 | "chrome_url_overrides": { 5 | "newtab": "homepage.html" 6 | }, 7 | "version": "2.1.5", 8 | "description": "__MSG_extension_description__", 9 | "icons": { 10 | "48": "images/icon-48.png", 11 | "96": "images/icon-96.png", 12 | "128": "images/icon-128.png", 13 | "256": "images/icon-256.png", 14 | "512": "images/icon-512.png" 15 | }, 16 | "permissions": ["nativeMessaging"], 17 | "background": { 18 | "service_worker": "background.js" 19 | }, 20 | "action": { 21 | "default_icon": { 22 | "16": "images/toolbar-icon-16.png", 23 | "19": "images/toolbar-icon-19.png", 24 | "32": "images/toolbar-icon-32.png", 25 | "38": "images/toolbar-icon-38.png", 26 | "48": "images/toolbar-icon-48.png", 27 | "72": "images/toolbar-icon-72.png" 28 | } 29 | }, 30 | "manifest_version": 3 31 | } 32 | -------------------------------------------------------------------------------- /HomepageExtension/Resources/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | } 4 | 5 | div { 6 | margin-top: 5px; 7 | } 8 | 9 | p { 10 | margin-top: 3px; 11 | } 12 | 13 | h1 { 14 | margin-bottom: 5px; 15 | margin-top: 3px; 16 | } 17 | 18 | .container { 19 | width: 100%; 20 | max-width: 600px; 21 | margin: 0 auto; 22 | padding: 20px; 23 | text-align: center; 24 | } 25 | 26 | @media (max-width: 600px) { 27 | .container { 28 | max-width: 90%; 29 | } 30 | } 31 | 32 | @media (prefers-color-scheme: dark) { 33 | body { 34 | background-color: #121212; 35 | color: white; 36 | color-scheme: dark; 37 | } 38 | } 39 | 40 | .deeplink { 41 | padding: 10px 20px; 42 | background-color: #007bff; 43 | color: #fff; 44 | border: none; 45 | border-radius: 5px; 46 | cursor: pointer; 47 | font-size: 16px; 48 | text-decoration: none; 49 | } 50 | 51 | input[type="url"] { 52 | width: 100%; 53 | max-width: 300px; 54 | padding: 10px; 55 | margin-bottom: 20px; 56 | border: 1px solid #ccc; 57 | border-radius: 5px; 58 | font-size: 16px; 59 | box-sizing: border-box; 60 | } -------------------------------------------------------------------------------- /HomepageExtension/Resources/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": { 3 | "message": "You have activated the Homepage extension. Open the app to set a homepage of your choice.", 4 | "openApp": "Open app" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HomepageExtension/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebExtensionHandler.swift 3 | // HomepageExtension 4 | // 5 | // Created by Ahnaf Mahmud on 04/04/2024. 6 | // 7 | 8 | import SafariServices 9 | import os.log 10 | 11 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { 12 | 13 | func beginRequest(with context: NSExtensionContext) { 14 | let request = context.inputItems.first as? NSExtensionItem 15 | let defaultPage = "default.html" 16 | 17 | let profile: UUID? 18 | if #available(iOS 17.0, macOS 14.0, *) { 19 | profile = request?.userInfo?[SFExtensionProfileKey] as? UUID 20 | } else { 21 | profile = request?.userInfo?["profile"] as? UUID 22 | } 23 | 24 | let message: String 25 | if #available(iOS 17.0, macOS 14.0, *) { 26 | let info = request?.userInfo?[SFExtensionMessageKey] as? AnyObject 27 | message = info?["message"] as? String ?? defaultPage 28 | } else { 29 | let info = request?.userInfo?["message"] as? AnyObject 30 | message = info?["message"] as? String ?? defaultPage 31 | } 32 | 33 | let describedMessage = String(describing: message) 34 | 35 | os_log( 36 | .default, 37 | "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", 38 | describedMessage, 39 | profile?.uuidString ?? "none" 40 | ) 41 | 42 | let response = NSExtensionItem() 43 | let url: String = UserDefaults.homepage ?? defaultPage 44 | response.userInfo = [ SFExtensionMessageKey: [ "url": url ] ] 45 | 46 | context.completeRequest(returningItems: [ response ], completionHandler: nil) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /HomepageTests/BundleExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleExtensionTests.swift 3 | // HomepageTests 4 | // 5 | // Created by Ahnaf Mahmud on 15/06/2024. 6 | // 7 | 8 | import Testing 9 | import Foundation 10 | 11 | @testable import Homepage 12 | 13 | struct BundleExtensionTests { 14 | @Test func canGetIconFileName() async throws { 15 | #expect(Bundle.iconFileName != nil) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HomepageTests/URLValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLValidatorTests.swift 3 | // HomepageTests 4 | // 5 | // Created by Ahnaf Mahmud on 11/06/2024. 6 | // 7 | 8 | import Testing 9 | @testable import Homepage 10 | 11 | struct URLValidatorTests { 12 | @Test func isCompleteURL() { 13 | #expect(URLValidator.isCompleteURL("http://example.com")) 14 | #expect(URLValidator.isCompleteURL("https://example.com")) 15 | } 16 | 17 | @Test func isIncompleteURL() { 18 | #expect(!URLValidator.isCompleteURL("example.com")) 19 | } 20 | 21 | @Test func aboutBlank() { 22 | #expect(URLValidator.isCompleteURL("about:blank")) 23 | #expect(URLValidator.isValidURL("about:blank")) 24 | } 25 | 26 | @Test func validHTTPURL() { 27 | #expect(URLValidator.isValidURL("http://example.com")) 28 | #expect(URLValidator.isValidURL("http://example.org")) 29 | #expect(URLValidator.isValidURL("http://sub.example.com")) 30 | #expect(URLValidator.isValidURL("http://example.com/path")) 31 | } 32 | 33 | @Test func validHTTPSURL() { 34 | #expect(URLValidator.isValidURL("https://example.com")) 35 | #expect(URLValidator.isValidURL("https://example.org")) 36 | #expect(URLValidator.isValidURL("https://sub.example.com")) 37 | #expect(URLValidator.isValidURL("https://example.com/path")) 38 | } 39 | 40 | @Test func validURLsWithPort() { 41 | #expect(URLValidator.isValidURL("http://example.com:80")) 42 | #expect(URLValidator.isValidURL("https://example.com:443")) 43 | #expect(URLValidator.isValidURL("http://sub.example.com:8080")) 44 | #expect(URLValidator.isValidURL("https://example.org:1234/path")) 45 | } 46 | 47 | @Test func invalidNoProtocolURL() { 48 | #expect(!URLValidator.isValidURL("example.com")) 49 | #expect(!URLValidator.isValidURL("sub.example.com")) 50 | #expect(!URLValidator.isValidURL("example.com/path")) 51 | #expect(!URLValidator.isValidURL("example.org/path?query=value")) 52 | #expect(!URLValidator.isValidURL("example.net#fragment")) 53 | } 54 | 55 | @Test func invalidURLs() { 56 | #expect(!URLValidator.isValidURL("ftp://example.com")) 57 | #expect(!URLValidator.isValidURL("http://example")) 58 | #expect(!URLValidator.isValidURL("example")) 59 | #expect(!URLValidator.isValidURL("https://example.c")) 60 | #expect(!URLValidator.isValidURL("https://")) 61 | #expect(!URLValidator.isValidURL("://example.com")) 62 | #expect(!URLValidator.isValidURL("http:/example.com")) 63 | #expect(!URLValidator.isValidURL("https:example.com")) 64 | } 65 | 66 | @Test func invalidURLsWithPort() { 67 | #expect(!URLValidator.isValidURL("http://example.com:")) 68 | #expect(!URLValidator.isValidURL("http://example.com:port")) 69 | #expect(!URLValidator.isValidURL("http://example.com:999999")) 70 | #expect(!URLValidator.isValidURL("http://example.com:-80")) 71 | } 72 | 73 | @Test func validURLsWithQueryAndFragment() { 74 | #expect(URLValidator.isValidURL("https://example.com/path?query=value")) 75 | #expect(URLValidator.isValidURL("https://example.com/path#fragment")) 76 | #expect(URLValidator.isValidURL("http://example.com/path?query=value#fragment")) 77 | } 78 | 79 | @Test func edgeCases() { 80 | #expect(!URLValidator.isValidURL("")) 81 | #expect(!URLValidator.isValidURL(" ")) 82 | #expect(!URLValidator.isValidURL("http://.com")) 83 | #expect(!URLValidator.isValidURL("http://example.")) 84 | } 85 | 86 | @Test func validIPAddresses() { 87 | #expect(URLValidator.isValidURL("http://127.0.0.1")) 88 | #expect(URLValidator.isValidURL("http://192.168.1.1")) 89 | #expect(URLValidator.isValidURL("https://255.255.255.255")) 90 | } 91 | 92 | @Test func validIPAddressesWithPort() { 93 | #expect(URLValidator.isValidURL("http://127.0.0.1:80")) 94 | #expect(URLValidator.isValidURL("https://192.168.1.1:443")) 95 | #expect(URLValidator.isValidURL("http://10.0.0.1:8080")) 96 | #expect(URLValidator.isValidURL("https://8.8.8.8:53/path")) 97 | } 98 | 99 | @Test func invalidIPAddresses() { 100 | #expect(!URLValidator.isValidURL("http://999.999.999.999")) 101 | #expect(!URLValidator.isValidURL("http://256.256.256.256")) 102 | #expect(!URLValidator.isValidURL("https://123.456.789.000")) 103 | #expect(!URLValidator.isValidURL("http://192.168.1")) 104 | #expect(!URLValidator.isValidURL("http://192.168.1.999")) 105 | } 106 | 107 | @Test func invalidIPAddressesWithPort() { 108 | #expect(!URLValidator.isValidURL("http://127.0.0.1:999999")) 109 | #expect(!URLValidator.isValidURL("http://192.168.1.1:-1")) 110 | #expect(!URLValidator.isValidURL("https://10.0.0.1:abcd")) 111 | #expect(!URLValidator.isValidURL("http://8.8.8.8:")) 112 | } 113 | 114 | @Test func validIPAddressesWithQueryAndFragment() { 115 | #expect(URLValidator.isValidURL("http://192.168.1.1/path")) 116 | #expect(URLValidator.isValidURL("http://10.0.0.1/path#fragment")) 117 | } 118 | 119 | @Test func validDataURL() { 120 | #expect(URLValidator.isValidURL("data:,Hello%2C%20World%21")) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /HomepageUITests/HomepageUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomepageUITests.swift 3 | // HomepageUITests 4 | // 5 | // Created by Ahnaf Mahmud on 01/02/2025. 6 | // 7 | 8 | import XCTest 9 | 10 | final class HomepageUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | continueAfterFailure = false 14 | } 15 | 16 | @MainActor 17 | func testViewComponents() throws { 18 | let app = XCUIApplication() 19 | app.launchArguments += ["ui-testing"] 20 | app.launch() 21 | 22 | let icon = app.images["Homepage icon"] 23 | XCTAssertTrue(icon.exists) 24 | 25 | let title = app.staticTexts["Title"] 26 | XCTAssertTrue(title.exists) 27 | 28 | let input = app.textFields["URL input"] 29 | XCTAssertTrue(input.exists) 30 | 31 | let saveButton = app.buttons["Save button"] 32 | XCTAssertTrue(saveButton.exists) 33 | 34 | let helpText = app.staticTexts["Help text"] 35 | XCTAssertTrue(helpText.exists) 36 | 37 | let aboutButton = app.buttons["About button"] 38 | XCTAssertTrue(aboutButton.exists) 39 | } 40 | 41 | @MainActor 42 | func testSavingURL() throws { 43 | let app = XCUIApplication() 44 | app.launchArguments += ["ui-testing"] 45 | app.launch() 46 | 47 | let input = app.textFields["URL input"] 48 | input.tap() 49 | input.typeText("https://google.com") 50 | 51 | let button = app.buttons["Save button"] 52 | button.tap() 53 | 54 | let alert = app.alerts["Homepage Saved"] 55 | XCTAssertTrue(alert.exists) 56 | } 57 | 58 | @MainActor 59 | func testSavingInvalidURL() throws { 60 | let app = XCUIApplication() 61 | app.launchArguments += ["ui-testing"] 62 | app.launch() 63 | 64 | let input = app.textFields["URL input"] 65 | input.tap() 66 | input.typeText("google") 67 | 68 | let button = app.buttons["Save button"] 69 | button.tap() 70 | 71 | let alert = app.alerts["Invalid URL"] 72 | XCTAssertTrue(alert.exists) 73 | } 74 | 75 | @MainActor 76 | func testSavingURLWithoutHttpPrefix() throws { 77 | let app = XCUIApplication() 78 | app.launchArguments += ["ui-testing"] 79 | app.launch() 80 | 81 | let input = app.textFields["URL input"] 82 | input.tap() 83 | input.typeText("google.com") 84 | 85 | let button = app.buttons["Save button"] 86 | button.tap() 87 | 88 | let alert = app.alerts["Homepage Saved"] 89 | XCTAssertTrue(alert.exists) 90 | } 91 | 92 | @MainActor 93 | func testAboutAlert() throws { 94 | let app = XCUIApplication() 95 | app.launchArguments += ["ui-testing"] 96 | app.launch() 97 | 98 | let aboutButton = app.buttons["About button"] 99 | aboutButton.tap() 100 | 101 | let alert = app.alerts["Homepage for Safari"] 102 | XCTAssertTrue(alert.exists) 103 | 104 | let closeButton = alert.buttons["Close"] 105 | XCTAssertTrue(closeButton.exists) 106 | 107 | let supportButton = alert.buttons["Support"] 108 | XCTAssertTrue(supportButton.exists) 109 | } 110 | 111 | @MainActor 112 | func testLaunchPerformance() throws { 113 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 114 | measure(metrics: [XCTApplicationLaunchMetric()]) { 115 | XCUIApplication().launch() 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /HomepageUITests/HomepageUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomepageUITestsLaunchTests.swift 3 | // HomepageUITests 4 | // 5 | // Created by Ahnaf Mahmud on 01/02/2025. 6 | // 7 | 8 | import XCTest 9 | 10 | final class HomepageUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | @MainActor 21 | func testLaunch() throws { 22 | let app = XCUIApplication() 23 | app.launch() 24 | 25 | let attachment = XCTAttachment(screenshot: app.screenshot()) 26 | attachment.name = "Launch Screen" 27 | attachment.lifetime = .keepAlways 28 | add(attachment) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HomepageWidget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0x71", 10 | "red" : "0x52" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HomepageWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /HomepageWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HomepageWidget/Assets.xcassets/HouseGear.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "symbol-rendering-intent" : "template" 8 | }, 9 | "symbols" : [ 10 | { 11 | "filename" : "HouseGear.svg", 12 | "idiom" : "universal" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /HomepageWidget/Assets.xcassets/HouseGear.symbolset/HouseGear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 22 | 23 | 24 | 25 | Weight/Scale Variations 26 | Ultralight 27 | Thin 28 | Light 29 | Regular 30 | Medium 31 | Semibold 32 | Bold 33 | Heavy 34 | Black 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Design Variations 46 | Symbols are supported in up to nine weights and three scales. 47 | For optimal layout with text and other symbols, vertically align 48 | symbols with the adjacent text. 49 | 50 | 51 | 52 | 53 | 54 | Margins 55 | Leading and trailing margins on the left and right side of each symbol 56 | can be adjusted by modifying the x-location of the margin guidelines. 57 | Modifications are automatically applied proportionally to all 58 | scales and weights. 59 | 60 | 61 | 62 | Exporting 63 | Symbols should be outlined when exporting to ensure the 64 | design is preserved when submitting to Xcode. 65 | Template v.6.0 66 | Requires Xcode 16 or greater 67 | Generated from 68 | Typeset at 100.0 points 69 | Small 70 | Medium 71 | Large 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /HomepageWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HomepageWidget/HomepageWidgetBundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomepageWidgetBundle.swift 3 | // HomepageWidget 4 | // 5 | // Created by Ahnaf Mahmud on 10/09/2024. 6 | // 7 | 8 | import WidgetKit 9 | import SwiftUI 10 | 11 | @main 12 | struct HomepageWidgetBundle: WidgetBundle { 13 | var body: some Widget { 14 | SetHomepageButton() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /HomepageWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.widgetkit-extension 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /HomepageWidget/Localizable.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "openApp" : { 5 | "extractionState" : "manual", 6 | "localizations" : { 7 | "en" : { 8 | "stringUnit" : { 9 | "state" : "translated", 10 | "value" : "Open App" 11 | } 12 | } 13 | } 14 | }, 15 | "setHomepage" : { 16 | "extractionState" : "manual", 17 | "localizations" : { 18 | "en" : { 19 | "stringUnit" : { 20 | "state" : "translated", 21 | "value" : "Set Homepage" 22 | } 23 | } 24 | } 25 | } 26 | }, 27 | "version" : "1.0" 28 | } -------------------------------------------------------------------------------- /HomepageWidget/SetHomepageButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetHomepageButton.swift 3 | // Homepage 4 | // 5 | // Created by Ahnaf Mahmud on 10/09/2024. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | /// A button to open the Homepage app 12 | struct SetHomepageButton: ControlWidget { 13 | var body: some ControlWidgetConfiguration { 14 | StaticControlConfiguration( 15 | kind: "com.ip18.Homepage.SetHomepageButton" 16 | ) { 17 | ControlWidgetButton(action: OpenAppIntent()) { 18 | Label("setHomepage", image: "HouseGear") 19 | } 20 | } 21 | .displayName("setHomepage") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ahnaf Mahmud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub](https://img.shields.io/github/license/infinitepower18/Homepage-MobileSafari) 2 | 3 | # Homepage for Safari iOS/visionOS 4 | 5 | With this extension, you can set any URL and Safari will automatically load your homepage upon opening a new tab. 6 | 7 | Unlike macOS, Safari on iOS and visionOS doesn't have the ability to set a homepage, so this extension was created to solve this. 8 | 9 | To load your homepage whenever a new tab is opened, ensure the extension is enabled and that this app is set under Open New Tabs in Safari Settings. 10 | 11 | The app also contains a shortcut to change your homepage URL, which can be used as part of other shortcuts and automations. 12 | 13 | ## Download 14 | 15 | [![image](https://ahnafmahmud.com/files/badges/AppStore.svg)](https://apps.apple.com/app/homepage-for-safari/id6481118559) 16 | 17 | [Privacy Policy](https://ahnafmahmud.com/apps/Homepage/PrivacyPolicy.html) 18 | 19 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F1K06VY) 20 | 21 | ## Translations 22 | 23 | Community translations are welcome. The locale file is located in `Homepage/Resources/Localizable.xcstrings`. If your language isn't yet supported, it will fallback to English. 24 | 25 | The default page when no homepage is set has its own locale file, which can be found in `HomepageExtension/Resources/translations.json`. 26 | 27 | If you are editing with Xcode, make sure you are using the latest version of Xcode 16. 28 | 29 | Future updates to strings will involve updating all languages using Google Translate or some other machine translation service. As you may know, these services are not always accurate, so if you see any inaccuracies, please make a pull request! 30 | -------------------------------------------------------------------------------- /Resources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | 10 | NSPrivacyAccessedAPIType 11 | NSPrivacyAccessedAPICategoryUserDefaults 12 | NSPrivacyAccessedAPITypeReasons 13 | 14 | 1C8F.1 15 | 16 | 17 | 18 | NSPrivacyTracking 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ci_scripts/ci_post_clone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | brew install swiftlint --------------------------------------------------------------------------------