diff --git a/BraveShared/BraveStrings.swift b/BraveShared/BraveStrings.swift index 2a329c20e9a..ae261ecd471 100644 --- a/BraveShared/BraveStrings.swift +++ b/BraveShared/BraveStrings.swift @@ -453,6 +453,17 @@ extension Strings { public static let DeleteBookmarksFolderAlertTitle = NSLocalizedString("DeleteBookmarksFolderAlertTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Delete Folder?", comment: "Title for the alert shown when the user tries to delete a bookmarks folder") public static let DeleteBookmarksFolderAlertMessage = NSLocalizedString("DeleteBookmarksFolderAlertMessage", tableName: "BraveShared", bundle: Bundle.braveShared, value: "This will delete all folders and bookmarks inside. Are you sure you want to continue?", comment: "Message for the alert shown when the user tries to delete a bookmarks folder") public static let YesDeleteButtonTitle = NSLocalizedString("YesDeleteButtonTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Yes, Delete", comment: "Button title to confirm the deletion of a bookmarks folder") + + public static let Close = NSLocalizedString("Close", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Close", comment: "Button title to close a menu.") + public static let OpenWebsite = NSLocalizedString("OpenWebsite", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Open Website", comment: "Button title to that opens a website.") + public static let ViewOn = NSLocalizedString("ViewOn", tableName: "BraveShared", bundle: Bundle.braveShared, value: "View on %@", comment: "Label that says where to view an item. '%@' is a placeholder and will include things like 'Instagram', 'unsplash'. The full label will look like 'View on Instagram'.") + public static let PhotoBy = NSLocalizedString("PhotoBy", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Photo by %@", comment: "Label that says who took a photograph that will be displayed to the user. '%@' is a placeholder and will include be a specific person's name, example 'Bill Gates'.") + public static let NewTabPageSettingsTitle = NSLocalizedString("NewTabPageSettingsTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "New Tab Page", comment: "Title on settings page to adjust the primary home screen functionality.") + public static let NewTabPageSettingsBackgroundImages = NSLocalizedString("NewTabPageSettingsBackgroundImages", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Show Background Images", comment: "A setting to enable or disable background images on the main screen.") + public static let NewTabPageSettingsSponsoredImages = NSLocalizedString("NewTabPageSettingsSponsoredImages", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Show Sponsored Images", comment: "A setting to enable or disable sponsored images from showing on the main screen.") + public static let NewTabPageSettingsAutoOpenKeyboard = NSLocalizedString("NewTabPageSettingsAutoOpenKeyboard", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Auto Open Keyboard", comment: "A setting to enable or disable the device's keyboard from opening automatically when creating a new tab.") + public static let NewTabPageShowMoreFavorites = NSLocalizedString("NewTabPageShowMoreFavorites", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Show More", comment: "A button title to show more bookmarks, that opens a new menu.") + } extension Strings { @@ -525,6 +536,7 @@ extension Strings { public static let NewBookmarkTitle = NSLocalizedString("NewBookmarkTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "New bookmark", comment: "Title for adding new bookmark") public static let NewFolderTitle = NSLocalizedString("NewFolderTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "New folder", comment: "Title for adding new folder") public static let EditBookmarkTitle = NSLocalizedString("EditBookmarkTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Edit bookmark", comment: "Title for editing a bookmark") + public static let EditFavoriteTitle = NSLocalizedString("EditFavoriteTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Edit favorite", comment: "Title for editing a favorite bookmark") public static let EditFolderTitle = NSLocalizedString("EditFolderTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Edit folder", comment: "Title for editing a folder") public static let HistoryScreenTitle = NSLocalizedString("HistoryScreenTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "History", comment: "Title for history screen") public static let BookmarksMenuItem = NSLocalizedString("BookmarksMenuItem", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Bookmarks", comment: "Title for bookmarks menu item") diff --git a/Client.xcodeproj/project.pbxproj b/Client.xcodeproj/project.pbxproj index 44ea53a900b..98a5bffb96b 100644 --- a/Client.xcodeproj/project.pbxproj +++ b/Client.xcodeproj/project.pbxproj @@ -594,11 +594,31 @@ 5D24AF8221BA459000F9506A /* BlocklistName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D24AF8121BA459000F9506A /* BlocklistName.swift */; }; 5D24AF8421BA489A00F9506A /* ContentBlocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D24AF8321BA489A00F9506A /* ContentBlocker.swift */; }; 5D24AF9021BA4D7700F9506A /* ContentBlockerRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D24AF8F21BA4D7700F9506A /* ContentBlockerRegion.swift */; }; + 5D53749C23846FFC00A2B587 /* anton-repponen.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53748D23846FFA00A2B587 /* anton-repponen.jpg */; }; + 5D53749D23846FFC00A2B587 /* dc-cavalleri.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53748E23846FFA00A2B587 /* dc-cavalleri.jpg */; }; + 5D53749E23846FFC00A2B587 /* louis-kim.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53748F23846FFA00A2B587 /* louis-kim.jpg */; }; + 5D53749F23846FFC00A2B587 /* Svalbard_Jerol_Soibam.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749023846FFA00A2B587 /* Svalbard_Jerol_Soibam.jpeg */; }; + 5D5374A023846FFC00A2B587 /* joe-gardner.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749123846FFB00A2B587 /* joe-gardner.jpg */; }; + 5D5374A123846FFC00A2B587 /* xavier-balderas-cejudo.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749223846FFB00A2B587 /* xavier-balderas-cejudo.jpg */; }; + 5D5374A223846FFC00A2B587 /* anders-jilden.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749323846FFB00A2B587 /* anders-jilden.jpg */; }; + 5D5374A323846FFC00A2B587 /* will_christiansen_glacier_peak.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749423846FFB00A2B587 /* will_christiansen_glacier_peak.jpg */; }; + 5D5374A423846FFC00A2B587 /* will_christiansen_ice.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749523846FFB00A2B587 /* will_christiansen_ice.jpg */; }; + 5D5374A523846FFC00A2B587 /* ben-karpinski.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749623846FFB00A2B587 /* ben-karpinski.jpg */; }; + 5D5374A623846FFC00A2B587 /* oliwier gesla.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749723846FFB00A2B587 /* oliwier gesla.jpg */; }; + 5D5374A723846FFC00A2B587 /* andreas-gucklhorn.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749823846FFC00A2B587 /* andreas-gucklhorn.jpg */; }; + 5D5374A823846FFC00A2B587 /* annie-spratt.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749923846FFC00A2B587 /* annie-spratt.jpg */; }; + 5D5374A923846FFC00A2B587 /* matt-palmer.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749A23846FFC00A2B587 /* matt-palmer.jpg */; }; + 5D5374AA23846FFC00A2B587 /* andy-mai.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D53749B23846FFC00A2B587 /* andy-mai.jpg */; }; + 5D5374B723887E7400A2B587 /* ntp-sponsored.json in Resources */ = {isa = PBXBuildFile; fileRef = 5D5374B623887E7400A2B587 /* ntp-sponsored.json */; }; + 5D5374B923887E7D00A2B587 /* EAFF_JA_soccer_Background_final.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5D5374B823887E7D00A2B587 /* EAFF_JA_soccer_Background_final.jpg */; }; + 5D5374BB238895B100A2B587 /* BackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5374BA238895B100A2B587 /* BackgroundImage.swift */; }; + 5D5E2361238C386B00AD6FBD /* NewTabPageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5E2360238C386B00AD6FBD /* NewTabPageTableViewController.swift */; }; 5D6DDEF3214003A6001FF0AE /* DAU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6DDEE6214003A6001FF0AE /* DAU.swift */; }; 5D6DDEFE2141B6A1001FF0AE /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6DDEF62141B6A0001FF0AE /* Preferences.swift */; }; 5D6DDF0021428CF0001FF0AE /* DAUTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6DDEFF21428CF0001FF0AE /* DAUTests.swift */; }; 5D7001C222249F7B00E576FB /* ShortNameToFileMapping.json in Resources */ = {isa = PBXBuildFile; fileRef = 5D7001BA22249F7A00E576FB /* ShortNameToFileMapping.json */; }; 5D8AE6AF230C76B60096C845 /* AppearanceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8AE6AE230C76B60096C845 /* AppearanceExtensions.swift */; }; + 5DB474F1237F4CC9007B7652 /* ntp-data.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DB474F0237F4CC9007B7652 /* ntp-data.json */; }; 5DE5250D218CE92C000DFAA6 /* block-ads.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DE52508218CE92B000DFAA6 /* block-ads.json */; }; 5DE5250E218CE92C000DFAA6 /* block-images.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DE52509218CE92B000DFAA6 /* block-images.json */; }; 5DE5250F218CE92C000DFAA6 /* upgrade-http.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DE5250A218CE92B000DFAA6 /* upgrade-http.json */; }; @@ -2010,11 +2030,31 @@ 5D24AF8121BA459000F9506A /* BlocklistName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocklistName.swift; sourceTree = ""; }; 5D24AF8321BA489A00F9506A /* ContentBlocker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlocker.swift; sourceTree = ""; }; 5D24AF8F21BA4D7700F9506A /* ContentBlockerRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRegion.swift; sourceTree = ""; }; + 5D53748D23846FFA00A2B587 /* anton-repponen.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "anton-repponen.jpg"; sourceTree = ""; }; + 5D53748E23846FFA00A2B587 /* dc-cavalleri.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "dc-cavalleri.jpg"; sourceTree = ""; }; + 5D53748F23846FFA00A2B587 /* louis-kim.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "louis-kim.jpg"; sourceTree = ""; }; + 5D53749023846FFA00A2B587 /* Svalbard_Jerol_Soibam.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Svalbard_Jerol_Soibam.jpeg; sourceTree = ""; }; + 5D53749123846FFB00A2B587 /* joe-gardner.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "joe-gardner.jpg"; sourceTree = ""; }; + 5D53749223846FFB00A2B587 /* xavier-balderas-cejudo.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "xavier-balderas-cejudo.jpg"; sourceTree = ""; }; + 5D53749323846FFB00A2B587 /* anders-jilden.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "anders-jilden.jpg"; sourceTree = ""; }; + 5D53749423846FFB00A2B587 /* will_christiansen_glacier_peak.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = will_christiansen_glacier_peak.jpg; sourceTree = ""; }; + 5D53749523846FFB00A2B587 /* will_christiansen_ice.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = will_christiansen_ice.jpg; sourceTree = ""; }; + 5D53749623846FFB00A2B587 /* ben-karpinski.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "ben-karpinski.jpg"; sourceTree = ""; }; + 5D53749723846FFB00A2B587 /* oliwier gesla.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "oliwier gesla.jpg"; sourceTree = ""; }; + 5D53749823846FFC00A2B587 /* andreas-gucklhorn.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "andreas-gucklhorn.jpg"; sourceTree = ""; }; + 5D53749923846FFC00A2B587 /* annie-spratt.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "annie-spratt.jpg"; sourceTree = ""; }; + 5D53749A23846FFC00A2B587 /* matt-palmer.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "matt-palmer.jpg"; sourceTree = ""; }; + 5D53749B23846FFC00A2B587 /* andy-mai.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "andy-mai.jpg"; sourceTree = ""; }; + 5D5374B623887E7400A2B587 /* ntp-sponsored.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "ntp-sponsored.json"; sourceTree = ""; }; + 5D5374B823887E7D00A2B587 /* EAFF_JA_soccer_Background_final.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = EAFF_JA_soccer_Background_final.jpg; sourceTree = ""; }; + 5D5374BA238895B100A2B587 /* BackgroundImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundImage.swift; sourceTree = ""; }; + 5D5E2360238C386B00AD6FBD /* NewTabPageTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageTableViewController.swift; sourceTree = ""; }; 5D6DDEE6214003A6001FF0AE /* DAU.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAU.swift; sourceTree = ""; }; 5D6DDEF62141B6A0001FF0AE /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 5D6DDEFF21428CF0001FF0AE /* DAUTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAUTests.swift; sourceTree = ""; }; 5D7001BA22249F7A00E576FB /* ShortNameToFileMapping.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ShortNameToFileMapping.json; sourceTree = ""; }; 5D8AE6AE230C76B60096C845 /* AppearanceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceExtensions.swift; sourceTree = ""; }; + 5DB474F0237F4CC9007B7652 /* ntp-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "ntp-data.json"; sourceTree = ""; }; 5DE52508218CE92B000DFAA6 /* block-ads.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "block-ads.json"; sourceTree = ""; }; 5DE52509218CE92B000DFAA6 /* block-images.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "block-images.json"; sourceTree = ""; }; 5DE5250A218CE92B000DFAA6 /* upgrade-http.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "upgrade-http.json"; sourceTree = ""; }; @@ -2744,6 +2784,11 @@ 0AADC4CD20D2A6A200FDE368 /* favorites */, 0AADC4D620D2B03900FDE368 /* BraveShieldStatsView.swift */, 0AADC4C720D2A55A00FDE368 /* FavoritesViewController.swift */, + 5D5374BA238895B100A2B587 /* BackgroundImage.swift */, + 5D5374B623887E7400A2B587 /* ntp-sponsored.json */, + 5DB474F0237F4CC9007B7652 /* ntp-data.json */, + 5D5374B42388733A00A2B587 /* NTO_Sponsored */, + 5DB474CE237F4BE9007B7652 /* NTP_Images */, ); path = HomePanel; sourceTree = ""; @@ -3315,6 +3360,7 @@ 2F44FC711A9E840300FD20CC /* SettingsNavigationController.swift */, 2F44FCC41A9E85E900FD20CC /* SettingsTableSectionHeaderFooterView.swift */, A1F66A7220DD71C400303328 /* SettingsViewController.swift */, + 5D5E2360238C386B00AD6FBD /* NewTabPageTableViewController.swift */, A1CA29C320E1746A00CB9126 /* OptionSelectionViewController.swift */, A16DC67E20E585D90069C8E1 /* PasscodeSettingsViewController.swift */, 0AEFB84822244135007AF600 /* AdblockDebugMenuTableViewController.swift */, @@ -3858,6 +3904,14 @@ path = Themes; sourceTree = ""; }; + 5D5374B42388733A00A2B587 /* NTO_Sponsored */ = { + isa = PBXGroup; + children = ( + 5D5374B823887E7D00A2B587 /* EAFF_JA_soccer_Background_final.jpg */, + ); + path = NTO_Sponsored; + sourceTree = ""; + }; 5D6DDEDF214003A6001FF0AE /* Analytics */ = { isa = PBXGroup; children = ( @@ -3873,6 +3927,28 @@ path = Analytics; sourceTree = ""; }; + 5DB474CE237F4BE9007B7652 /* NTP_Images */ = { + isa = PBXGroup; + children = ( + 5D53749323846FFB00A2B587 /* anders-jilden.jpg */, + 5D53749823846FFC00A2B587 /* andreas-gucklhorn.jpg */, + 5D53749B23846FFC00A2B587 /* andy-mai.jpg */, + 5D53749923846FFC00A2B587 /* annie-spratt.jpg */, + 5D53748D23846FFA00A2B587 /* anton-repponen.jpg */, + 5D53749623846FFB00A2B587 /* ben-karpinski.jpg */, + 5D53748E23846FFA00A2B587 /* dc-cavalleri.jpg */, + 5D53749123846FFB00A2B587 /* joe-gardner.jpg */, + 5D53748F23846FFA00A2B587 /* louis-kim.jpg */, + 5D53749A23846FFC00A2B587 /* matt-palmer.jpg */, + 5D53749723846FFB00A2B587 /* oliwier gesla.jpg */, + 5D53749023846FFA00A2B587 /* Svalbard_Jerol_Soibam.jpeg */, + 5D53749423846FFB00A2B587 /* will_christiansen_glacier_peak.jpg */, + 5D53749523846FFB00A2B587 /* will_christiansen_ice.jpg */, + 5D53749223846FFB00A2B587 /* xavier-balderas-cejudo.jpg */, + ); + path = NTP_Images; + sourceTree = ""; + }; 5DE7688520B3456D00FF5533 /* BraveShared */ = { isa = PBXGroup; children = ( @@ -5364,12 +5440,14 @@ files = ( 5E0FCD212342526700AC831E /* FullscreenHelper.js in Resources */, D38A1EE01CB458EC0080C842 /* CertError.html in Resources */, + 5D5374A523846FFC00A2B587 /* ben-karpinski.jpg in Resources */, 0BA1E0301B051A07007675AF /* NetError.css in Resources */, 59871C1921E4D83900316590 /* block-cookies.json in Resources */, 3BC659491E5BA4AE006D560F /* TopSites in Resources */, E4B7B77E1A793CF20022C5E0 /* FiraSans-SemiBold.ttf in Resources */, F84B220B1A0910F600AAB793 /* Images.xcassets in Resources */, 2755AB7D23107BC600F0721F /* AdsReporting.js in Resources */, + 5DB474F1237F4CC9007B7652 /* ntp-data.json in Resources */, 595E0EDB21CAD97C00813D49 /* CookieControl.js in Resources */, 0A0D3D3C21A4BE6400BEE65B /* adblock-list.txt in Resources */, 27658272217130D900754B2F /* GlobalSign_r3.cer in Resources */, @@ -5384,16 +5462,21 @@ E4CD9F541A71506400318571 /* Reader.html in Resources */, C6345ECB2113B3A000CFB983 /* SearchPlugins in Resources */, F930CDAC227000F200A23FE1 /* U2F.js in Resources */, + 5D5374B923887E7D00A2B587 /* EAFF_JA_soccer_Background_final.jpg in Resources */, 5D7001C222249F7B00E576FB /* ShortNameToFileMapping.json in Resources */, 4422D55921BFFB7F00BF1855 /* unicode.py in Resources */, 7B2142FE1E5E055000CDD3FC /* InfoPlist.strings in Resources */, E69922171B94E3EF007C480D /* Licenses.html in Resources */, + 5D5374A223846FFC00A2B587 /* anders-jilden.jpg in Resources */, E4CD9F5B1A71506C00318571 /* Reader.css in Resources */, + 5D5374B723887E7400A2B587 /* ntp-sponsored.json in Resources */, D0FCF8061FE4772D004A7995 /* AllFramesAtDocumentEnd.js in Resources */, 27658273217130D900754B2F /* DigiCertHighAssurance.cer in Resources */, E4B7B7611A793CF20022C5E0 /* CharisSILB.ttf in Resources */, 4422D56E21BFFB7F00BF1855 /* make_perl_groups.pl in Resources */, E4B7B7621A793CF20022C5E0 /* CharisSILBI.ttf in Resources */, + 5D5374A723846FFC00A2B587 /* andreas-gucklhorn.jpg in Resources */, + 5D53749E23846FFC00A2B587 /* louis-kim.jpg in Resources */, 27658271217130D900754B2F /* GlobalSign_r1.cer in Resources */, 39F4C0FA2045D87400746155 /* FocusHelper.js in Resources */, E4B7B7861A793CF20022C5E0 /* FiraSans-UltraLight.ttf in Resources */, @@ -5405,27 +5488,38 @@ 59D4161B232681080087A6E1 /* Localizable.strings in Resources */, D0FCF8081FE4772D004A7995 /* MainFrameAtDocumentStart.js in Resources */, 0A0D3D5021A5609600BEE65B /* SafeBrowsingError.html in Resources */, + 5D5374A623846FFC00A2B587 /* oliwier gesla.jpg in Resources */, 74821FFE1DB6D3AC00EEEA72 /* MailSchemes.plist in Resources */, 5EC1DDBC2356883800CA4028 /* onboarding-shields.json in Resources */, + 5D53749C23846FFC00A2B587 /* anton-repponen.jpg in Resources */, 0AA4AA99231849450009AAEF /* ABPFilterParserData.dat in Resources */, 5EC1DDBE2356892900CA4028 /* onboarding-ads.json in Resources */, 4422D57621C05DE600BF1855 /* httpse.leveldb.tgz in Resources */, FA9294011D6584A200AC8D33 /* QRCode.xcassets in Resources */, D308EE561CBF0BF5006843F2 /* CertError.css in Resources */, 4422D4AD21BFFB7600BF1855 /* LICENSE in Resources */, + 5D5374AA23846FFC00A2B587 /* andy-mai.jpg in Resources */, E4B7B7641A793CF20022C5E0 /* CharisSILR.ttf in Resources */, 5D21AE082303B70E00B30D97 /* Themes in Resources */, 4422D56D21BFFB7F00BF1855 /* make_unicode_groups.py in Resources */, E4B7B7681A793CF20022C5E0 /* FiraSans-Bold.ttf in Resources */, E4B7B7781A793CF20022C5E0 /* FiraSans-Italic.ttf in Resources */, + 5D53749D23846FFC00A2B587 /* dc-cavalleri.jpg in Resources */, 5EC1DDBD2356888000CA4028 /* onboarding-rewards.json in Resources */, 0BA1E00E1B03FB0B007675AF /* NetError.html in Resources */, 5DE5250E218CE92C000DFAA6 /* block-images.json in Resources */, + 5D5374A823846FFC00A2B587 /* annie-spratt.jpg in Resources */, E4A961381AC06FA50069AD6F /* ReaderViewLoading.html in Resources */, 59C8119922D754D4005D5BBC /* BraveShared.strings in Resources */, E4ECCDAE1AB131770005E717 /* FiraSans-Medium.ttf in Resources */, E4424B3C1AC71FB400F44C38 /* FiraSans-Book.ttf in Resources */, + 5D5374A423846FFC00A2B587 /* will_christiansen_ice.jpg in Resources */, + 5D53749F23846FFC00A2B587 /* Svalbard_Jerol_Soibam.jpeg in Resources */, 0AA21E4C2302C4CC00358988 /* webauth_touch_key.json in Resources */, + 5D5374A023846FFC00A2B587 /* joe-gardner.jpg in Resources */, + 5D5374A123846FFC00A2B587 /* xavier-balderas-cejudo.jpg in Resources */, + 5D5374A923846FFC00A2B587 /* matt-palmer.jpg in Resources */, + 5D5374A323846FFC00A2B587 /* will_christiansen_glacier_peak.jpg in Resources */, 5DE52511218CE92C000DFAA6 /* block-trackers.json in Resources */, 39A35AED1C0662A3006B9E87 /* SpotlightHelper.js in Resources */, D0FCF8071FE4772D004A7995 /* MainFrameAtDocumentEnd.js in Resources */, @@ -5916,6 +6010,7 @@ 592F521E2217327C0078395E /* HttpCookieExtension.swift in Sources */, 4422D4D721BFFB7600BF1855 /* table.cc in Sources */, 0A4BEF62221B26610005551A /* LocalAdblockResourceProtocol.swift in Sources */, + 5D5E2361238C386B00AD6FBD /* NewTabPageTableViewController.swift in Sources */, E4CD9F6D1A77DD2800318571 /* ReaderModeStyleViewController.swift in Sources */, D0FCF7F51FE45842004A7995 /* UserScriptManager.swift in Sources */, E4A960061ABB9C450069AD6F /* ReaderModeUtils.swift in Sources */, @@ -6128,6 +6223,7 @@ 4422D50121BFFB7600BF1855 /* repair.cc in Sources */, E660BDD91BB06521009AC090 /* TabsButton.swift in Sources */, 4422D55721BFFB7F00BF1855 /* unicode_groups.cc in Sources */, + 5D5374BB238895B100A2B587 /* BackgroundImage.swift in Sources */, 4422D55421BFFB7E00BF1855 /* re2_fuzzer.cc in Sources */, 2F44FCCB1A9E972E00FD20CC /* SearchEnginePicker.swift in Sources */, C6B81B8C212D989200996084 /* ImageCacheOptions.swift in Sources */, diff --git a/Client/Application/ClientPreferences.swift b/Client/Application/ClientPreferences.swift index 7b1814db7e4..0474390db39 100644 --- a/Client/Application/ClientPreferences.swift +++ b/Client/Application/ClientPreferences.swift @@ -96,5 +96,13 @@ extension Preferences { /// The toggles states for clear private data screen static let clearPrivateDataToggles = Option<[Bool]>(key: "privacy.clear-data-toggles", default: []) } + final class NewTabPage { + /// Whether bookmark image are enabled / shown + static let backgroundImages = Option(key: "newtabpage.background-images", default: true) + /// Whether sponsored images are included into the background image rotation + static let backgroundSponsoredImages = Option(key: "newtabpage.background-sponsored-images", default: true) + /// Whether the iOS keyboard auto-opens on a NTP or not + static let autoOpenKeyboard = Option(key: "newtabpage.auto-open-keyboard", default: false) + } } diff --git a/Client/Application/NavigationRouter.swift b/Client/Application/NavigationRouter.swift index bc52ab09502..9f178858284 100644 --- a/Client/Application/NavigationRouter.swift +++ b/Client/Application/NavigationRouter.swift @@ -76,11 +76,11 @@ enum NavigationPath: Equatable { if let newURL = url { bvc.switchToTabForURLOrOpen(newURL, isPrivate: isPrivate, isPrivileged: false) } else { - bvc.openBlankNewTab(focusLocationField: true, isPrivate: isPrivate) + bvc.openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: isPrivate) } } private static func handleText(text: String, with bvc: BrowserViewController) { - bvc.openBlankNewTab(focusLocationField: true, searchFor: text) + bvc.openBlankNewTab(attemptLocationFieldFocus: true, searchFor: text) } } diff --git a/Client/Application/QuickActions.swift b/Client/Application/QuickActions.swift index c2312bb3cd8..3283cc219cd 100644 --- a/Client/Application/QuickActions.swift +++ b/Client/Application/QuickActions.swift @@ -64,6 +64,6 @@ class QuickActions: NSObject { } fileprivate func handleOpenNewTab(withBrowserViewController bvc: BrowserViewController, isPrivate: Bool) { - bvc.openBlankNewTab(focusLocationField: true, isPrivate: isPrivate) + bvc.openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: isPrivate) } } diff --git a/Client/Assets/Images.xcassets/ntp-search.imageset/Contents.json b/Client/Assets/Images.xcassets/ntp-search.imageset/Contents.json new file mode 100644 index 00000000000..e6b0dc8da0d --- /dev/null +++ b/Client/Assets/Images.xcassets/ntp-search.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "search@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "search@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Client/Assets/Images.xcassets/ntp-search.imageset/search@2x.png b/Client/Assets/Images.xcassets/ntp-search.imageset/search@2x.png new file mode 100644 index 00000000000..61b1afe3f75 Binary files /dev/null and b/Client/Assets/Images.xcassets/ntp-search.imageset/search@2x.png differ diff --git a/Client/Assets/Images.xcassets/ntp-search.imageset/search@3x.png b/Client/Assets/Images.xcassets/ntp-search.imageset/search@3x.png new file mode 100644 index 00000000000..ab3b9fe197c Binary files /dev/null and b/Client/Assets/Images.xcassets/ntp-search.imageset/search@3x.png differ diff --git a/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json b/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json index a737edbd84a..695cd41ff73 100644 --- a/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json +++ b/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json @@ -28,7 +28,7 @@ "ads": "0xFB542B", "trackers": "0x742BC4", "httpse": "0xA0A5EB", - "timeSaved": "0x1E2029" + "timeSaved": "0xffffff" } }, "images": { diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index 4dff67b885b..b6df2958702 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -334,6 +334,7 @@ class BrowserViewController: UIViewController { if showToolbar { toolbar = BottomToolbarView() + toolbar?.setSearchButtonState(url: tabManager.selectedTab?.url) footer.addSubview(toolbar!) toolbar?.tabToolbarDelegate = self @@ -1327,22 +1328,23 @@ class BrowserViewController: UIViewController { switchToPrivacyMode(isPrivate: isPrivate) _ = tabManager.addTabAndSelect(request, isPrivate: isPrivate) if url == nil && NewTabAccessors.getNewTabPage() == .blankPage { - topToolbar.tabLocationViewDidTapLocation(topToolbar.locationView) + focusLocationField() } } - func openBlankNewTab(focusLocationField: Bool, isPrivate: Bool = false, searchFor searchText: String? = nil) { + func openBlankNewTab(attemptLocationFieldFocus: Bool, isPrivate: Bool = false, searchFor searchText: String? = nil) { popToBVC() openURLInNewTab(nil, isPrivate: isPrivate, isPrivileged: true) let freshTab = tabManager.selectedTab - if focusLocationField { + // Focus field only if requested and background images are not supported + if attemptLocationFieldFocus && Preferences.NewTabPage.autoOpenKeyboard.value { DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { // Without a delay, the text field fails to become first responder // Check that the newly created tab is still selected. // This let's the user spam the Cmd+T button without lots of responder changes. guard freshTab == self.tabManager.selectedTab else { return } - self.topToolbar.tabLocationViewDidTapLocation(self.topToolbar.locationView) + self.focusLocationField() if let text = searchText { self.topToolbar.setLocation(text, search: true) } @@ -1473,6 +1475,9 @@ class BrowserViewController: UIViewController { } if let url = webView.url { + // Whether to show search icon or + icon + toolbar?.setSearchButtonState(url: url) + if !url.isErrorPageURL, !url.isAboutHomeURL, !url.isFileURL { // Fire the readability check. This is here and not in the pageShow event handler in ReaderMode.js anymore // because that event wil not always fire due to unreliable page caching. This will either let us know that @@ -1521,6 +1526,10 @@ class BrowserViewController: UIViewController { } } + private func focusLocationField() { + topToolbar.tabLocationViewDidTapLocation(topToolbar.locationView) + } + // MARK: - Browser PIN Callout private var isBrowserLockEnabled: Bool { @@ -1617,12 +1626,12 @@ extension BrowserViewController: ClipboardBarDisplayHandlerDelegate { extension BrowserViewController: QRCodeViewControllerDelegate { func didScanQRCodeWithURL(_ url: URL) { - openBlankNewTab(focusLocationField: false) + openBlankNewTab(attemptLocationFieldFocus: false) finishEditingAndSubmit(url, visitType: VisitType.typed) } func didScanQRCodeWithText(_ text: String) { - openBlankNewTab(focusLocationField: false) + openBlankNewTab(attemptLocationFieldFocus: false) submitSearchText(text) } } @@ -1864,8 +1873,11 @@ extension BrowserViewController: TopToolbarDelegate { // TODO: This logic should be fully abstracted away and share logic from current MenuViewController // See: https://github.com/brave/brave-ios/issues/1452 - func topToolbarDidTapBookmarkButton(_ topToolbar: TopToolbarView) { - let vc = BookmarksViewController(folder: nil, isPrivateBrowsing: PrivateBrowsingManager.shared.isPrivateBrowsing) + func topToolbarDidTapBookmarkButton(_ topToolbar: TopToolbarView?, favorites: Bool) { + let mode: BookmarksViewController.Mode = favorites ? .favorites : .bookmarks(inFolder: nil) + + let vc = BookmarksViewController(mode: mode, + isPrivateBrowsing: PrivateBrowsingManager.shared.isPrivateBrowsing) vc.toolbarUrlActionsDelegate = self let nav = SettingsNavigationController(rootViewController: vc) @@ -1887,6 +1899,10 @@ extension BrowserViewController: TopToolbarDelegate { } extension BrowserViewController: ToolbarDelegate { + func tabToolbarDidPressSearch(_ tabToolbar: ToolbarProtocol, button: UIButton) { + focusLocationField() + } + func tabToolbarDidPressBack(_ tabToolbar: ToolbarProtocol, button: UIButton) { tabManager.selectedTab?.goBack() } @@ -1938,7 +1954,7 @@ extension BrowserViewController: ToolbarDelegate { } func tabToolbarDidPressAddTab(_ tabToolbar: ToolbarProtocol, button: UIButton) { - self.openBlankNewTab(focusLocationField: true, isPrivate: PrivateBrowsingManager.shared.isPrivateBrowsing) + self.openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: PrivateBrowsingManager.shared.isPrivateBrowsing) } func tabToolbarDidLongPressAddTab(_ tabToolbar: ToolbarProtocol, button: UIButton) { @@ -1951,13 +1967,13 @@ extension BrowserViewController: ToolbarDelegate { let newPrivateTabAction = UIAlertAction(title: Strings.NewPrivateTabTitle, style: .default, handler: { [unowned self] _ in // BRAVE TODO: Add check for DuckDuckGo popup (and based on 1.6, whether the browser lock is enabled?) // before focusing on the url bar - self.openBlankNewTab(focusLocationField: true, isPrivate: true) + self.openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: true) }) actions.append(newPrivateTabAction) } let bottomActionTitle = PrivateBrowsingManager.shared.isPrivateBrowsing ? Strings.NewPrivateTabTitle : Strings.NewTabTitle actions.append(UIAlertAction(title: bottomActionTitle, style: .default, handler: { [unowned self] _ in - self.openBlankNewTab(focusLocationField: true, isPrivate: PrivateBrowsingManager.shared.isPrivateBrowsing) + self.openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: PrivateBrowsingManager.shared.isPrivateBrowsing) })) return actions } @@ -2220,7 +2236,8 @@ extension BrowserViewController: TabManagerDelegate { wv.accessibilityIdentifier = nil wv.removeFromSuperview() } - + + toolbar?.setSearchButtonState(url: selected?.url) if let tab = selected, let webView = tab.webView { updateURLBar() @@ -3315,6 +3332,10 @@ extension BrowserViewController: TopSitesDelegate { func didTapDuckDuckGoCallout() { presentDuckDuckGoCallout(force: true) } + + func didTapShowMoreFavorites() { + topToolbarDidTapBookmarkButton(nil, favorites: true) + } } extension BrowserViewController: PreferencesObserver { diff --git a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+KeyCommands.swift b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+KeyCommands.swift index 743d482ced4..cb1d5d27db2 100644 --- a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+KeyCommands.swift +++ b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+KeyCommands.swift @@ -37,13 +37,13 @@ extension BrowserViewController { } @objc private func newTabKeyCommand() { - openBlankNewTab(focusLocationField: true, isPrivate: PrivateBrowsingManager.shared.isPrivateBrowsing) + openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: PrivateBrowsingManager.shared.isPrivateBrowsing) } @objc private func newPrivateTabKeyCommand() { // NOTE: We cannot and should not distinguish between "new-tab" and "new-private-tab" // when recording telemetry for key commands. - openBlankNewTab(focusLocationField: true, isPrivate: true) + openBlankNewTab(attemptLocationFieldFocus: true, isPrivate: true) } @objc private func closeTabKeyCommand() { diff --git a/Client/Frontend/Browser/HomePanel/BackgroundImage.swift b/Client/Frontend/Browser/HomePanel/BackgroundImage.swift new file mode 100644 index 00000000000..72ce39068c7 --- /dev/null +++ b/Client/Frontend/Browser/HomePanel/BackgroundImage.swift @@ -0,0 +1,165 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Shared +import BraveShared + +class BackgroundImage { + + struct Background { + let imageFileName: String + let center: CGFloat + let isSponsored: Bool + let credit: (name: String, url: String?)? + + lazy var image: UIImage? = { + return UIImage(named: imageFileName) + }() + } + + // Data is static to avoid duplicate loads + + /// This is the number of backgrounds that must appear before a background can be repeated. + /// So if background `3` is shown, it cannot be shown until this many backgrounds are shown, then `3` can be shown again. + /// This does not apply to sponsored images. + /// This is reset on each launch, so `3` can be shown again if app is removed from memory. + /// This number _must_ be less than the number of backgrounds! + private static let numberOfDuplicateAvoidance = 6 + private static let sponsorshipShowRate = 4 // e.g. 4 == 25% or "every 4th image" + + let info: Background? + static var hasSponsor: Bool { sponsors?.count ?? 0 > 0 } + private static var sponsors: [Background]? + private static var standardBackgrounds: [Background]? + + // This is used to prevent the same handful of backgrounds from being shown. + // It will track the last N pictures that have been shown and prevent them from being shown + // until they are 'old' and dropped from this array. + // Currently only supports normal backgrounds, as sponsored images are not supposed to be duplicate. + // This can 'easily' be adjusted to support both sets by switching to String, and using filePath to identify uniqueness. + private static var lastBackgroundChoices = [Int]() + + init(sponsoredFilePath: String = "ntp-sponsored", backgroundFilePath: String = "ntp-data") { + + if !Preferences.NewTabPage.backgroundImages.value { + // Do absolutely nothing + self.info = nil + return + } + + // Setting up normal backgrounds + if BackgroundImage.standardBackgrounds == nil { + BackgroundImage.standardBackgrounds = BackgroundImage.generateStandardData(file: backgroundFilePath) + } + + // Setting up sponsored backgrounds + if BackgroundImage.sponsors == nil && Preferences.NewTabPage.backgroundSponsoredImages.value { + BackgroundImage.sponsors = BackgroundImage.generateSponsoredData(file: sponsoredFilePath) + } + + self.info = BackgroundImage.randomBackground() + } + + private static func generateSponsoredData(file: String) -> [Background] { + guard let json = BackgroundImage.loadImageJSON(file: file), + let region = NSLocale.current.regionCode else { + return [] + } + + let dateFormatter = DateFormatter().then { + $0.locale = Locale(identifier: "en_US_POSIX") + $0.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + $0.calendar = Calendar(identifier: .gregorian) + } + + let today = Date() + // Filter down to only regional supported items + let regionals = json.filter { ($0["regions"] as? [String])?.contains(region) == true } + + // Filter down to sponsors that fit the date requirements + let live = regionals.filter { item in + guard let dates = item["dates"] as? [String: String], + let start = dateFormatter.date(from: dates["start"] ?? ""), + let end = dateFormatter.date(from: dates["end"] ?? "") else { + return false + } + + return today > start && today < end + } + + return generateBackgroundData(data: live, isSponsored: true) + } + + private static func generateStandardData(file: String) -> [Background] { + guard let json = BackgroundImage.loadImageJSON(file: file) else { return [] } + return generateBackgroundData(data: json, isSponsored: false) + } + + private static func generateBackgroundData(data: [[String: Any]], isSponsored: Bool) -> [Background] { + return data.compactMap { item in + guard let image = item["image"] as? String, + let center = item["center"] as? CGFloat else { return nil } + + if let credit = item["credit"] as? [String: String], + let name = credit["name"] { + return Background(imageFileName: image, center: center, isSponsored: isSponsored, credit: (name, credit["url"])) + } + + return Background(imageFileName: image, center: center, isSponsored: isSponsored, credit: nil) + } + } + + private static func randomBackground() -> Background? { + // Determine what type of background to display + let useSponsor = hasSponsor && Int.random(in: 0..= 0, "randomBackgroundIndex was nil, this is terrible.") + + // This index is now added to 'past' tracking list to prevent duplicates + lastBackgroundChoices.append(randomBackgroundIndex) + // Trimming to fixed length to release older backgrounds + lastBackgroundChoices = lastBackgroundChoices.suffix(numberOfDuplicateAvoidance) + } + + // Item is returned based on our random index. + // Could generally use `randomElement()`, but for non-sponsored images, certain indeces are ignored. + return dataSet[safe: randomBackgroundIndex] + } + + private static func loadImageJSON(file: String) -> [[String: Any]]? { + guard let filePath = Bundle.main.path(forResource: file, ofType: "json") else { + return nil + } + + do { + let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath)) + let json = try JSONSerialization.jsonObject(with: fileData, options: []) as? [[String: Any]] + return json + } catch { + Logger.browserLogger.error("Failed to get bundle path for \(file)") + } + + return nil + } + + /// This is a temp work around to address the weird settings requirements for the initial sponsored immage + /// support in these super specific regions, as it is also to ignore if a sponsored item is actually available. + static var showSponsoredSetting: Bool { + let regions = ["JP", "CN", "KR", "KP", "TW", "MN", "MO", "GU", "MP", "SG", "IN", "PK", "MV", "BD", "BT", "NP", "UZ"] + return regions.contains(NSLocale.current.regionCode ?? "") + } +} diff --git a/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift b/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift index 74468f20c63..51546d6fb5e 100644 --- a/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift +++ b/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift @@ -144,7 +144,7 @@ class StatView: UIView { fileprivate var titleLabel: UILabel = { let label = UILabel() - label.textColor = UX.HomePanel.StatTitleColor + label.appearanceTextColor = .white label.textAlignment = .center label.numberOfLines = 0 label.font = UIFont.systemFont(ofSize: 10, weight: UIFont.Weight.medium) diff --git a/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift b/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift index eb897ed4ba6..9372b7dc213 100644 --- a/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift +++ b/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift @@ -5,23 +5,24 @@ import UIKit import Shared import BraveShared -import XCGLogger import Storage import Deferred import Data +import SnapKit private let log = Logger.browserLogger protocol TopSitesDelegate: AnyObject { func didSelect(input: String) func didTapDuckDuckGoCallout() + func didTapShowMoreFavorites() } class FavoritesViewController: UIViewController, Themeable { private struct UI { static let statsHeight: CGFloat = 110.0 static let statsBottomMargin: CGFloat = 5 - static let searchEngineCalloutPadding: CGFloat = 30.0 + static let searchEngineCalloutPadding: CGFloat = 120.0 } weak var delegate: TopSitesDelegate? @@ -52,6 +53,48 @@ class FavoritesViewController: UIViewController, Themeable { $0.autoresizingMask = [.flexibleWidth] } + private lazy var favoritesOverflowButton = RoundInterfaceView().then { + let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + let button = UIButton(type: .system).then { + $0.setTitle(Strings.NewTabPageShowMoreFavorites, for: .normal) + $0.appearanceTextColor = .white + $0.titleLabel?.font = UIFont.systemFont(ofSize: 12.0, weight: .medium) + $0.addTarget(self, action: #selector(showFavorites), for: .touchUpInside) + } + + $0.clipsToBounds = true + + $0.addSubview(blur) + $0.addSubview(button) + + blur.snp.makeConstraints { $0.edges.equalToSuperview() } + button.snp.makeConstraints { $0.edges.equalToSuperview() } + } + + // Needs to be own variable in order to dynamically set title contents + private lazy var imageCreditInternalButton = UIButton(type: .system).then { + $0.appearanceTextColor = .white + $0.titleLabel?.font = UIFont.systemFont(ofSize: 12.0, weight: .medium) + $0.addTarget(self, action: #selector(showImageCredit), for: .touchUpInside) + } + + private lazy var imageCreditButton = UIView().then { + let blur = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + $0.clipsToBounds = true + $0.layer.cornerRadius = 4 + + $0.addSubview(blur) + $0.addSubview(imageCreditInternalButton) + + blur.snp.makeConstraints { $0.edges.equalToSuperview() } + imageCreditInternalButton.snp.makeConstraints { + $0.top.bottom.equalToSuperview() + let padding = 10 + $0.left.equalToSuperview().offset(padding) + $0.right.equalToSuperview().inset(padding) + } + } + private let ddgLogo = UIImageView(image: #imageLiteral(resourceName: "duckduckgo")) private let ddgLabel = UILabel().then { @@ -61,16 +104,33 @@ class FavoritesViewController: UIViewController, Themeable { $0.text = Strings.DDG_promotion } - private lazy var ddgButton = UIControl().then { - $0.addTarget(self, action: #selector(showDDGCallout), for: .touchUpInside) + private lazy var ddgButton = RoundInterfaceView().then { + let blur = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + let actualButton = UIButton(type: .system).then { + $0.addTarget(self, action: #selector(showDDGCallout), for: .touchUpInside) + } + $0.clipsToBounds = true + + $0.addSubview(blur) + $0.addSubview(actualButton) + + blur.snp.makeConstraints { $0.edges.equalToSuperview() } + actualButton.snp.makeConstraints { $0.edges.equalToSuperview() } } @objc private func showDDGCallout() { delegate?.didTapDuckDuckGoCallout() } + @objc private func showFavorites() { + delegate?.didTapShowMoreFavorites() + } + // MARK: - Init/lifecycle + private var backgroundViewInfo: (imageView: UIImageView, portraitCenterConstraint: Constraint)? + private var backgroundImage = BackgroundImage() + private let profile: Profile init(profile: Profile, dataSource: FavoritesDataSource = FavoritesDataSource()) { @@ -83,7 +143,7 @@ class FavoritesViewController: UIViewController, Themeable { name: Notification.Name.TopSitesConversion, object: nil) $0.addObserver(self, selector: #selector(privateBrowsingModeChanged), name: Notification.Name.PrivacyModeChanged, object: nil) - } + } } @objc func existingUserTopSitesConversion() { @@ -104,14 +164,30 @@ class FavoritesViewController: UIViewController, Themeable { override func viewDidLoad() { super.viewDidLoad() + view.clipsToBounds = true + + setupBackgroundImage() + // Setup gradient regardless of background image, can internalize to setup background image if only wanted for images. + view.layer.addSublayer(gradientOverlay()) let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(gesture:))) collection.addGestureRecognizer(longPressGesture) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(gesture:))) + // Adding a clear background for tap gestures, otherwise impossible to tap other buttons / icons on NTP + let background = UIView() + background.backgroundColor = .clear + background.addGestureRecognizer(tapGesture) + collection.backgroundView = background + view.addSubview(collection) collection.dataSource = dataSource dataSource.collectionView = collection + dataSource.favoriteDeletedHandler = { [weak self] in + self?.favoritesOverflowButton.isHidden = self?.dataSource.hasOverflow == false + } + // Could setup as section header but would need to use flow layout, // Auto-layout subview within collection doesn't work properly, // Quick-and-dirty layout here. @@ -124,19 +200,32 @@ class FavoritesViewController: UIViewController, Themeable { braveShieldStatsView.frame = statsViewFrame collection.addSubview(braveShieldStatsView) + collection.addSubview(favoritesOverflowButton) collection.addSubview(ddgButton) + collection.addSubview(imageCreditButton) ddgButton.addSubview(ddgLogo) ddgButton.addSubview(ddgLabel) makeConstraints() + Preferences.NewTabPage.backgroundImages.observe(from: self) + Preferences.NewTabPage.backgroundSponsoredImages.observe(from: self) + + // Doens't this get called twice? collectionContentSizeObservation = collection.observe(\.contentSize, options: [.new, .initial]) { [weak self] _, _ in self?.updateDuckDuckGoButtonLayout() } updateDuckDuckGoVisibility() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // Need to reload data after modals are closed for potential orientation change + // e.g. if in landscape, open portrait modal, close, the layout attempt to access an invalid indexpath + collection.reloadData() + } + private var collectionContentSizeObservation: NSKeyValueObservation? override func viewDidLayoutSubviews() { @@ -144,6 +233,23 @@ class FavoritesViewController: UIViewController, Themeable { // This makes collection view layout to recalculate its cell size. collection.collectionViewLayout.invalidateLayout() + favoritesOverflowButton.isHidden = !dataSource.hasOverflow + collection.reloadSections(IndexSet(arrayLiteral: 0)) + + if let backgroundImageView = backgroundViewInfo?.imageView, let image = backgroundImageView.image { + // Need to calculate the sizing difference between `image` and `imageView` to determine the pixel difference ratio + let sizeRatio = backgroundImageView.frame.size.width / image.size.width + + // Center point of image is not center point of view. + // Take `0` for example, if specying `0`, setting centerX to 0, it is not attempting to place the left + // side of the image to the middle (e.g. left justifying), it is instead trying to move the image view's + // center to `0`, shifting the image _to_ the left, and making more of the image's right side visible. + // Therefore specifying `0` should take the imageView's left and pinning it to view's center. + + // So basically the movement needs to be "inverted" (hence negation) + let imageViewOffset = sizeRatio * -(backgroundImage.info?.center ?? 0) + backgroundViewInfo?.portraitCenterConstraint.update(offset: imageViewOffset) + } } private func updateDuckDuckGoButtonLayout() { @@ -161,6 +267,7 @@ class FavoritesViewController: UIViewController, Themeable { switch gesture.state { case .began: guard let selectedIndexPath = collection.indexPathForItem(at: gesture.location(in: collection)) else { + handleLongGestureForBackground(gesture: gesture) break } @@ -175,6 +282,44 @@ class FavoritesViewController: UIViewController, Themeable { } } + @objc func handleTapGesture(gesture: UITapGestureRecognizer) { + // Tap gesture only actionable with sponsored images. + if gesture.state == .ended && backgroundImage.info?.isSponsored == true { + showImageCredit() + } + } + + /// Handles long press gesture for background credit + func handleLongGestureForBackground(gesture: UILongPressGestureRecognizer) { + if gesture.state != .began { + return + } + showImageCredit() + } + + @objc fileprivate func showImageCredit() { + guard let credit = backgroundImage.info?.credit else { + // No gesture action of no credit available + return + } + + let alert = UIAlertController(title: credit.name, message: nil, preferredStyle: .actionSheet) + + if let creditWebsite = credit.url, let creditURL = URL(string: creditWebsite) { + alert.addAction(UIAlertAction(title: Strings.OpenWebsite, style: .default) { [weak self] _ in + self?.delegate?.didSelect(input: creditWebsite) + }) + alert.message = String(format: Strings.ViewOn, creditURL.hostSLD) + } + + alert.popoverPresentationController?.sourceView = view + alert.popoverPresentationController?.sourceRect = CGRect(origin: view.center, size: .zero) + alert.popoverPresentationController?.permittedArrowDirections = [.down, .up] + alert.addAction(UIAlertAction(title: Strings.Close, style: .default, handler: nil)) + + present(alert, animated: true, completion: nil) + } + // MARK: - Constraints setup fileprivate func makeConstraints() { collection.snp.makeConstraints { make in @@ -188,11 +333,28 @@ class FavoritesViewController: UIViewController, Themeable { } ddgLabel.snp.makeConstraints { make in - make.top.right.bottom.equalTo(0) + make.top.bottom.equalTo(0) + make.right.equalToSuperview().offset(-5) make.left.equalTo(self.ddgLogo.snp.right).offset(5) make.width.equalTo(180) make.centerY.equalTo(self.ddgLogo) } + + favoritesOverflowButton.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalTo(ddgButton.snp.top).offset(-90) + $0.height.equalTo(24) + $0.width.equalTo(84) + } + + imageCreditButton.snp.makeConstraints { + let borderPadding = 20 + $0.bottom.equalTo(self.view.snp.bottom).inset(borderPadding) + $0.left.equalToSuperview().offset(borderPadding) + $0.height.equalTo(24) + // Width and therefore, right constraint is determined by the actual button inside of this view + // button is resized from text content, and this superview is pinned to that width. + } } // MARK: - Private browsing modde @@ -216,6 +378,97 @@ class FavoritesViewController: UIViewController, Themeable { collection.collectionViewLayout.invalidateLayout() } + private func setupImageCredit() { + var hideImageCredit = true + defer { + imageCreditButton.isHidden = hideImageCredit + } + + guard let info = backgroundImage.info, let name = info.credit?.name else { return } + + hideImageCredit = info.isSponsored + let photoByText = String(format: Strings.PhotoBy, name) + imageCreditInternalButton.setTitle(photoByText, for: .normal) + } + + private func setupBackgroundImage() { + guard var background = backgroundImage.info, + let image = background.image else { + return + } + + setupImageCredit() + let imageAspectRatio = image.size.width / image.size.height + let imageView = UIImageView(image: image) + + imageView.contentMode = UIImageView.ContentMode.scaleAspectFit + // Make sure it goes to the back + view.insertSubview(imageView, at: 0) + + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.snp.makeConstraints { + + // Determines the height of the content + // `999` priority is required for landscape, since top/bottom constraints no longer the most important + // using `1000` / `required` would cause constraint conflicts (with `centerY` in landscape), and + // using `high` is not enough either. + $0.top.bottom.equalToSuperview().priority(ConstraintPriority(999)) + + // In portrait `top`/`bottom` is enough, however, when switching to landscape, those constraints + // don't force centering, so this is used as a stronger constraint to center in landscape/portrait + $0.centerY.equalToSuperview() + + // Width of the image view is determined by the forced height constraint and the literal image ratio + $0.width.equalTo(imageView.snp.height).multipliedBy(imageAspectRatio) + + // These are required constraints to avoid a bad center pushing the image out of view. + // if a center of `-100` or `100000` is specified, these override to keep entire background covered by image. + // The left side cannot exceed `0` (or superview's left side), otherwise whitespace will be shown on left. + $0.left.lessThanOrEqualToSuperview() + + // the right side cannot drop under `width` (or superview's right side), otherwise whitespace will be shown on right. + $0.right.greaterThanOrEqualToSuperview() + + // If for some reason the image cannot fill full width (e.g. not a landscape image), then these constraints + // will fail. A constraint will be broken, since cannot keep both left and right side's pinned + // (due to the width multiplier being < 1 + + // Using `high` priority so that it will not be applied / broken if out-of-bounds. + // Offset updated / calculated during view layout as views are not setup yet. + let backgroundConstraint = $0.left.equalTo(view.snp.centerX).priority(ConstraintPriority.high).constraint + self.backgroundViewInfo = (imageView, backgroundConstraint) + } + } + + fileprivate func resetBackground() { + self.backgroundViewInfo?.imageView.removeFromSuperview() + self.backgroundViewInfo = nil + + // Flush background logic, this handles preference adjustments for us, so will update necessary, needed info. + backgroundImage = BackgroundImage() + + setupBackgroundImage() + } + + fileprivate func gradientOverlay() -> CAGradientLayer { + + // Fades from half-black to transparent + let colorTop = UIColor(white: 0.0, alpha: 0.5).cgColor + let colorBottom = UIColor(white: 0.0, alpha: 0.0).cgColor + + let gl = CAGradientLayer() + gl.colors = [colorTop, colorBottom] + + // Gradient cover percentage + gl.locations = [0.0, 0.5] + + // Making a squrare to handle rotation events + let maxSide = max(view.bounds.height, view.bounds.width) + gl.frame = CGRect(size: CGSize(width: maxSide, height: maxSide)) + + return gl + } + // MARK: DuckDuckGo func shouldShowDuckDuckGoCallout() -> Bool { @@ -247,7 +500,7 @@ extension FavoritesViewController: UICollectionViewDelegateFlowLayout { let width = collection.frame.width let padding: CGFloat = traitCollection.horizontalSizeClass == .compact ? 6 : 20 - let cellWidth = floor(width - padding) / CGFloat(columnsPerRow) + let cellWidth = floor(width - padding) / CGFloat(dataSource.columnsPerRow) // The tile's height is determined the aspect ratio of the thumbnails width. We also take into account // some padding between the title and the image. let cellHeight = floor(cellWidth / (CGFloat(FavoriteCell.imageAspectRatio) - 0.1)) @@ -259,36 +512,6 @@ extension FavoritesViewController: UICollectionViewDelegateFlowLayout { guard let favoriteCell = cell as? FavoriteCell else { return } favoriteCell.delegate = self } - - fileprivate var columnsPerRow: Int { - let size = collection.bounds.size - let traitCollection = collection.traitCollection - var cols = 0 - if traitCollection.horizontalSizeClass == .compact { - // Landscape iPhone - if traitCollection.verticalSizeClass == .compact { - cols = 5 - } - // Split screen iPad width - else if size.widthLargerOrEqualThanHalfIPad() { - cols = 4 - } - // iPhone portrait - else { - cols = 3 - } - } else { - // Portrait iPad - if size.height > size.width { - cols = 4 - } - // Landscape iPad - else { - cols = 5 - } - } - return cols + 1 - } } extension FavoritesViewController: FavoriteCellDelegate { @@ -346,6 +569,12 @@ extension FavoritesViewController: FavoriteCellDelegate { } } +extension FavoritesViewController: PreferencesObserver { + func preferencesDidChange(for key: String) { + self.resetBackground() + } +} + extension CGSize { public func widthLargerOrEqualThanHalfIPad() -> Bool { let halfIPadSize: CGFloat = 507 diff --git a/Client/Frontend/Browser/HomePanel/NTO_Sponsored/EAFF_JA_soccer_Background_final.jpg b/Client/Frontend/Browser/HomePanel/NTO_Sponsored/EAFF_JA_soccer_Background_final.jpg new file mode 100644 index 00000000000..027fce6070e Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTO_Sponsored/EAFF_JA_soccer_Background_final.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/Svalbard_Jerol_Soibam.jpeg b/Client/Frontend/Browser/HomePanel/NTP_Images/Svalbard_Jerol_Soibam.jpeg new file mode 100644 index 00000000000..742aeead0f7 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/Svalbard_Jerol_Soibam.jpeg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/anders-jilden.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/anders-jilden.jpg new file mode 100644 index 00000000000..cbbb8ea7975 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/anders-jilden.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/andreas-gucklhorn.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/andreas-gucklhorn.jpg new file mode 100644 index 00000000000..ccadcc961da Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/andreas-gucklhorn.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/andy-mai.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/andy-mai.jpg new file mode 100644 index 00000000000..4d3f0fdb7c7 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/andy-mai.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/annie-spratt.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/annie-spratt.jpg new file mode 100644 index 00000000000..5050cb8c2d1 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/annie-spratt.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/anton-repponen.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/anton-repponen.jpg new file mode 100644 index 00000000000..93d313ca463 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/anton-repponen.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/ben-karpinski.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/ben-karpinski.jpg new file mode 100644 index 00000000000..78e050b3ad8 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/ben-karpinski.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/dc-cavalleri.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/dc-cavalleri.jpg new file mode 100644 index 00000000000..a95565626e5 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/dc-cavalleri.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/joe-gardner.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/joe-gardner.jpg new file mode 100644 index 00000000000..ee106037172 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/joe-gardner.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/louis-kim.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/louis-kim.jpg new file mode 100644 index 00000000000..1c99d6d180f Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/louis-kim.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/matt-palmer.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/matt-palmer.jpg new file mode 100644 index 00000000000..49978e3dc9c Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/matt-palmer.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/oliwier gesla.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/oliwier gesla.jpg new file mode 100644 index 00000000000..d8c14e082de Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/oliwier gesla.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/will_christiansen_glacier_peak.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/will_christiansen_glacier_peak.jpg new file mode 100644 index 00000000000..699a3f0dc18 Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/will_christiansen_glacier_peak.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/will_christiansen_ice.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/will_christiansen_ice.jpg new file mode 100644 index 00000000000..87dce04f37c Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/will_christiansen_ice.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/NTP_Images/xavier-balderas-cejudo.jpg b/Client/Frontend/Browser/HomePanel/NTP_Images/xavier-balderas-cejudo.jpg new file mode 100644 index 00000000000..0de0e2cae4c Binary files /dev/null and b/Client/Frontend/Browser/HomePanel/NTP_Images/xavier-balderas-cejudo.jpg differ diff --git a/Client/Frontend/Browser/HomePanel/favorites/FavoriteCell.swift b/Client/Frontend/Browser/HomePanel/favorites/FavoriteCell.swift index 5af87b0dbb4..291c59b880f 100644 --- a/Client/Frontend/Browser/HomePanel/favorites/FavoriteCell.swift +++ b/Client/Frontend/Browser/HomePanel/favorites/FavoriteCell.swift @@ -19,7 +19,6 @@ class FavoriteCell: UICollectionViewCell { /// Ratio of width:height of the thumbnail image. static let cornerRadius: CGFloat = 8 - static let labelColor = UIAccessibility.isDarkerSystemColorsEnabled ? UX.GreyJ : UX.GreyH static let labelAlignment: NSTextAlignment = .center static let labelInsets = UIEdgeInsets(top: 0, left: 3, bottom: 2, right: 3) @@ -35,7 +34,7 @@ class FavoriteCell: UICollectionViewCell { let textLabel = UILabel().then { $0.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.vertical) $0.font = DynamicFontHelper.defaultHelper.DefaultSmallFont - $0.textColor = UI.labelColor + $0.appearanceTextColor = .white $0.textAlignment = UI.labelAlignment $0.lineBreakMode = NSLineBreakMode.byWordWrapping $0.numberOfLines = 2 @@ -130,9 +129,6 @@ class FavoriteCell: UICollectionViewCell { super.prepareForReuse() editButton.isHidden = true backgroundColor = UIColor.clear - textLabel.font = DynamicFontHelper.defaultHelper.DefaultSmallFont - textLabel.textColor = - PrivateBrowsingManager.shared.isPrivateBrowsing ? UX.Favorites.cellLabelColorPrivate : UX.Favorites.cellLabelColorNormal imageView.backgroundColor = UIColor.clear imageView.image = nil } diff --git a/Client/Frontend/Browser/HomePanel/favorites/FavoritesDataSource.swift b/Client/Frontend/Browser/HomePanel/favorites/FavoritesDataSource.swift index 80c576b8397..1f42152fb80 100644 --- a/Client/Frontend/Browser/HomePanel/favorites/FavoritesDataSource.swift +++ b/Client/Frontend/Browser/HomePanel/favorites/FavoritesDataSource.swift @@ -11,6 +11,8 @@ private let log = Logger.browserLogger class FavoritesDataSource: NSObject, UICollectionViewDataSource { var frc: NSFetchedResultsController? weak var collectionView: UICollectionView? + + var favoriteDeletedHandler: (() -> Void)? var isEditing: Bool = false { didSet { @@ -37,6 +39,45 @@ class FavoritesDataSource: NSObject, UICollectionViewDataSource { } } + /// The number of times that each row contains + var columnsPerRow: Int { + guard let size = collectionView?.bounds.size, + let traitCollection = collectionView?.traitCollection else { + return 0 + } + + var cols = 0 + if traitCollection.horizontalSizeClass == .compact { + // Landscape iPhone + if traitCollection.verticalSizeClass == .compact { + cols = 5 + } + // Split screen iPad width + else if size.widthLargerOrEqualThanHalfIPad() { + cols = 4 + } + // iPhone portrait + else { + cols = 3 + } + } else { + // Portrait iPad + if size.height > size.width { + cols = 4 + } + // Landscape iPad + else { + cols = 5 + } + } + return cols + 1 + } + + /// If there are more favorites than are being shown + var hasOverflow: Bool { + return columnsPerRow < frc?.fetchedObjects?.count ?? 0 + } + func refetch() { try? frc?.performFetch() } @@ -48,7 +89,7 @@ class FavoritesDataSource: NSObject, UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return frc?.fetchedObjects?.count ?? 0 + return min(columnsPerRow, frc?.fetchedObjects?.count ?? 0) } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { @@ -89,9 +130,14 @@ extension FavoritesDataSource: NSFetchedResultsControllerDelegate { collectionView?.insertItems(at: [indexPath]) } case .delete: - if let indexPath = indexPath { - collectionView?.deleteItems(at: [indexPath]) - } + // Not all favorites must be visible at the time, so we can't just call `deleteItems` here. + // Example: + // There's 10 favorites total, 4 are visible on iPhone. + // We delete second favorite(index=1), visible favorites count should still be 4. + // Favorites from places 3 and 4 are moved back to 2 and 3 + // a new, previously hidden favorite item is now shown at position 4. + collectionView?.reloadData() + favoriteDeletedHandler?() case .update: if let indexPath = indexPath, let cell = collectionView?.cellForItem(at: indexPath) as? FavoriteCell { configureCell(cell: cell, at: indexPath) diff --git a/Client/Frontend/Browser/HomePanel/ntp-data.json b/Client/Frontend/Browser/HomePanel/ntp-data.json new file mode 100644 index 00000000000..b9d19c2f267 --- /dev/null +++ b/Client/Frontend/Browser/HomePanel/ntp-data.json @@ -0,0 +1,122 @@ +[ + { + "image": "anders-jilden.jpg", + "center": 1200, + "credit": { + "name": "Anders Jildén", + "url": "https://unsplash.com/@andersjilden?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "andreas-gucklhorn.jpg", + "center": 1160, + "credit": { + "name": "Andreas Gücklhorn", + "url": "https://unsplash.com/@draufsicht?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "andy-mai.jpg", + "center": 988, + "credit": { + "name": "Andy Mai", + "url": "https://unsplash.com/@veroz?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "annie-spratt.jpg", + "center": 682, + "credit": { + "name": "Annie Spratt", + "url": "https://unsplash.com/@anniespratt?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "anton-repponen.jpg", + "center": 2185, + "credit": { + "name": "Anton Repponen", + "url": "https://unsplash.com/@repponen?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "ben-karpinski.jpg", + "center": 815, + "credit": { + "name": "Ben Karpinski", + "url": "http://www.benkarpinski.com/landscapes" + } + }, + { + "image": "dc-cavalleri.jpg", + "center": 480, + "credit": { + "name": "D.C. Cavalleri", + "url": "https://gradivis.com" + } + }, + { + "image": "joe-gardner.jpg", + "center": 1277, + "credit": { + "name": "Joe Gardner", + "url": "https://unsplash.com/@josephgardnerphotography?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "louis-kim.jpg", + "center": 300, + "credit": { + "name": "Louis Kim", + "url": "" + } + }, + { + "image": "matt-palmer.jpg", + "center": 1205, + "credit": { + "name": "Matt Palmer", + "url": "https://unsplash.com/@mattpalmer?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + }, + { + "image": "oliwier gesla.jpg", + "center": 780, + "credit": { + "name": "Oliwier Gesla", + "url": "https://www.instagram.com/oliwiergesla/" + } + }, + { + "image": "Svalbard_Jerol_Soibam.jpeg", + "center": 1280, + "credit": { + "name": "Jerol Soibam", + "url": "https://www.instagram.com/jerol_soibam/" + } + }, + { + "image": "will_christiansen_glacier_peak.jpg", + "center": 1630, + "credit": { + "name": "Will Christiansen", + "url": "http://www.theskyfolk.com/" + } + }, + { + "image": "will_christiansen_ice.jpg", + "center": 1330, + "credit": { + "name": "Will Christiansen", + "url": "http://www.theskyfolk.com/" + } + }, + { + "image": "xavier-balderas-cejudo.jpg", + "center": 1975, + "credit": { + "name": "Xavier Balderas Cejudo", + "url": "https://unsplash.com/@xavibalderas?utm_source=unsplash&utm_medium=referral&utm_content=credit" + } + } +] diff --git a/Client/Frontend/Browser/HomePanel/ntp-sponsored.json b/Client/Frontend/Browser/HomePanel/ntp-sponsored.json new file mode 100644 index 00000000000..15ef619efd2 --- /dev/null +++ b/Client/Frontend/Browser/HomePanel/ntp-sponsored.json @@ -0,0 +1,33 @@ +[ + { + "image": "EAFF_JA_soccer_Background_final.jpg", + "center": 1280, + "credit": { + "name": "EAFF 2019", + "url": "https://eaff.com/competitions/eaff2019/" + }, + "dates": { + "start": "2019-11-25T00:00:13+09:00", + "end": "2019-12-18T00:00:00+09:00" + }, + "regions": [ + "JP", + "CN", + "KR", + "KP", + "TW", + "MN", + "MO", + "GU", + "MP", + "SG", + "IN", + "PK", + "MV", + "BD", + "BT", + "NP", + "UZ" + ] + } +] \ No newline at end of file diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift index 2437837c6ca..e92f717703a 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift @@ -15,6 +15,9 @@ class BottomToolbarView: UIView, ToolbarProtocol { let backButton = ToolbarButton(top: false) let shareButton = ToolbarButton(top: false) let addTabButton = ToolbarButton(top: false) + let searchButton = ToolbarButton(top: false).then { + $0.isHidden = true + } let menuButton = ToolbarButton(top: false) let actionButtons: [Themeable & UIButton] @@ -22,7 +25,7 @@ class BottomToolbarView: UIView, ToolbarProtocol { private let contentView = UIStackView() fileprivate override init(frame: CGRect) { - actionButtons = [backButton, forwardButton, addTabButton, tabsButton, menuButton] + actionButtons = [backButton, forwardButton, addTabButton, searchButton, tabsButton, menuButton] super.init(frame: frame) setupAccessibility() @@ -34,6 +37,17 @@ class BottomToolbarView: UIView, ToolbarProtocol { addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didSwipeToolbar(_:)))) } + + private var isSearchButtonEnabled: Bool = false { + didSet { + addTabButton.isHidden = isSearchButtonEnabled + searchButton.isHidden = !addTabButton.isHidden + } + } + + func setSearchButtonState(url: URL?) { + isSearchButtonEnabled = url?.isAboutHomeURL == true + } override func updateConstraints() { contentView.snp.makeConstraints { make in @@ -49,6 +63,7 @@ class BottomToolbarView: UIView, ToolbarProtocol { tabsButton.accessibilityIdentifier = "TabToolbar.tabsButton" shareButton.accessibilityIdentifier = "TabToolbar.shareButton" addTabButton.accessibilityIdentifier = "TabToolbar.addTabButton" + searchButton.accessibilityIdentifier = "TabToolbar.searchButton" accessibilityNavigationStyle = .combined accessibilityLabel = Strings.TabToolbarAccessibilityLabel } diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditBookmarkTableViewController.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditBookmarkTableViewController.swift index 97fbb6f0fa9..b0debd2c3fc 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditBookmarkTableViewController.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditBookmarkTableViewController.swift @@ -50,7 +50,7 @@ class AddEditBookmarkTableViewController: UITableViewController { return BookmarkDetailsView(title: title, url: url) case .addFolder(let title): return FolderDetailsViewTableViewCell(title: title, viewHeight: UX.cellHeight) - case .editBookmark(let bookmark): + case .editBookmark(let bookmark), .editFavorite(let bookmark): return BookmarkDetailsView(title: bookmark.displayTitle, url: bookmark.url) case .editFolder(let folder): return FolderDetailsViewTableViewCell(title: folder.displayTitle, viewHeight: UX.cellHeight) @@ -72,7 +72,7 @@ class AddEditBookmarkTableViewController: UITableViewController { private var specialButtonsCount: Int { switch mode { case .addFolder(_), .editFolder(_): return 1 - case .addBookmark(_, _), .editBookmark(_): return 3 + case .addBookmark(_, _), .editBookmark(_), .editFavorite(_): return 3 } } @@ -286,6 +286,25 @@ class AddEditBookmarkTableViewController: UITableViewController { case .folder(let folderSaveLocation): folder.updateWithNewLocation(customTitle: title, url: nil, location: folderSaveLocation) } + + case .editFavorite(let favorite): + guard let urlString = bookmarkDetailsView.urlTextField?.text, + let url = URL(string: urlString) ?? urlString.bookmarkletURL else { + return earlyReturn() + } + + if !favorite.existsInPersistentStore() { break } + + switch saveLocation { + case .rootLevel: + favorite.delete() + Bookmark.add(url: url, title: title) + case .favorites: + favorite.update(customTitle: title, url: url.absoluteString) + case .folder(let folder): + favorite.delete() + Bookmark.add(url: url, title: title, parentFolder: folder) + } } // If there are two or more children, we don't want to dismiss the whole view controller. diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarkEditMode.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarkEditMode.swift index 1f6ea8295f9..6865614ca85 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarkEditMode.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarkEditMode.swift @@ -13,6 +13,7 @@ enum BookmarkEditMode { case addFolder(title: String) case editBookmark(_ bookmark: Bookmark) case editFolder(_ folder: Bookmark) + case editFavorite(_ favorite: Bookmark) /// Returns a initial, default save location if none is provided var initialSaveLocation: BookmarkSaveLocation { @@ -24,6 +25,8 @@ enum BookmarkEditMode { return folderOrRoot(bookmarkOrFolder: bookmark) case .editFolder(let folder): return folderOrRoot(bookmarkOrFolder: folder) + case .editFavorite(_): + return .favorites } } @@ -44,6 +47,7 @@ enum BookmarkEditMode { case .addFolder(_): return Strings.NewFolderTitle case .editBookmark(_): return Strings.EditBookmarkTitle case .editFolder(_): return Strings.EditFolderTitle + case .editFavorite(_): return Strings.EditFavoriteTitle } } @@ -56,7 +60,7 @@ enum BookmarkEditMode { // Order of cells matters. switch self { case .addFolder(_), .editFolder(_): return [.rootLevel] - case .addBookmark(_), .editBookmark(_): return [.addFolder, .favorites, .rootLevel] + case .addBookmark(_), .editBookmark(_), .editFavorite(_): return [.addFolder, .favorites, .rootLevel] } } } diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift index 7931d405bac..6090122008d 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift @@ -13,63 +13,76 @@ private let log = Logger.browserLogger class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtocol { /// Called when the bookmarks are updated via some user input (i.e. Delete, edit, etc.) - var bookmarksDidChange: (() -> Void)? + var bookmarksDidChange: (() -> Void)? + weak var toolbarUrlActionsDelegate: ToolbarUrlActionsDelegate? + var bookmarksFRC: NSFetchedResultsController? - weak var toolbarUrlActionsDelegate: ToolbarUrlActionsDelegate? - - var bookmarksFRC: NSFetchedResultsController? - - lazy var editBookmarksButton = UIBarButtonItem().then { + lazy var editBookmarksButton: UIBarButtonItem? = UIBarButtonItem().then { $0.image = #imageLiteral(resourceName: "edit").template $0.style = .plain $0.target = self $0.action = #selector(onEditBookmarksButton) } - lazy var addFolderButton = UIBarButtonItem().then { + lazy var addFolderButton: UIBarButtonItem? = UIBarButtonItem().then { $0.image = #imageLiteral(resourceName: "bookmarks_newfolder_icon").template $0.style = .plain $0.target = self $0.action = #selector(onAddBookmarksFolderButton) } - weak var addBookmarksFolderOkAction: UIAlertAction? - - var isEditingIndividualBookmark: Bool = false + weak var addBookmarksFolderOkAction: UIAlertAction? + + var isEditingIndividualBookmark: Bool = false - var currentFolder: Bookmark? + var currentFolder: Bookmark? /// Certain bookmark actions are different in private browsing mode. let isPrivateBrowsing: Bool - - init(folder: Bookmark?, isPrivateBrowsing: Bool) { - self.isPrivateBrowsing = isPrivateBrowsing - super.init(nibName: nil, bundle: nil) + let mode: Mode - self.currentFolder = folder - self.title = folder?.displayTitle ?? Strings.Bookmarks - self.bookmarksFRC = Bookmark.frc(parentFolder: folder) - self.bookmarksFRC?.delegate = self - - // FIXME: NotificationMainThreadContextSignificantlyChanged is gone - } + enum Mode { + // Set nil for root level bookmarks. + case bookmarks(inFolder: Bookmark?) + case favorites + } - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + init(mode: Mode, isPrivateBrowsing: Bool) { + self.isPrivateBrowsing = isPrivateBrowsing + self.mode = mode + + super.init(nibName: nil, bundle: nil) + + switch mode { + case .bookmarks(let folder): + self.currentFolder = folder + self.title = folder?.displayTitle ?? Strings.Bookmarks + self.bookmarksFRC = Bookmark.frc(parentFolder: folder) + case .favorites: + title = Strings.FavoritesRootLevelCellTitle + bookmarksFRC = Bookmark.frc(forFavorites: true, parentFolder: nil) + addFolderButton = nil + } + + self.bookmarksFRC?.delegate = self + } - override func viewDidLoad() { - super.viewDidLoad() - - tableView.allowsSelectionDuringEditing = true - - setUpToolbar() - updateEditBookmarksButtonStatus() - } + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.allowsSelectionDuringEditing = true + + setUpToolbar() + updateEditBookmarksButtonStatus() + } private func updateEditBookmarksButtonStatus() { guard let count = bookmarksFRC?.fetchedObjects?.count else { return } - editBookmarksButton.isEnabled = count != 0 + editBookmarksButton?.isEnabled = count != 0 if tableView.isEditing && count == 0 { disableTableEditingMode() } @@ -79,7 +92,7 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco var padding: UIBarButtonItem { return UIBarButtonItem.fixedSpace(5) } let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil) - let items = [padding, addFolderButton, flexibleSpace, editBookmarksButton, padding] + let items = [padding, addFolderButton, flexibleSpace, editBookmarksButton, padding].compactMap { $0 } setToolbarItems(items, animated: true) } @@ -89,7 +102,12 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco // Recreate the frc if it was previously removed // (when user navigated into a nested folder for example) if bookmarksFRC == nil { - bookmarksFRC = Bookmark.frc(parentFolder: currentFolder) + switch mode { + case .bookmarks(_): + bookmarksFRC = Bookmark.frc(parentFolder: currentFolder) + case .favorites: + bookmarksFRC = Bookmark.frc(forFavorites: true, parentFolder: nil) + } bookmarksFRC?.delegate = self } try self.bookmarksFRC?.performFetch() @@ -126,13 +144,13 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco updateEditBookmarksButton(editMode) resetCellLongpressGesture(tableView.isEditing) - editBookmarksButton.isEnabled = bookmarksFRC?.fetchedObjects?.count != 0 - addFolderButton.isEnabled = !editMode + editBookmarksButton?.isEnabled = bookmarksFRC?.fetchedObjects?.count != 0 + addFolderButton?.isEnabled = !editMode } func updateEditBookmarksButton(_ tableIsEditing: Bool) { - self.editBookmarksButton.title = tableIsEditing ? Strings.Done : Strings.Edit - self.editBookmarksButton.style = tableIsEditing ? .done : .plain + self.editBookmarksButton?.title = tableIsEditing ? Strings.Done : Strings.Edit + self.editBookmarksButton?.style = tableIsEditing ? .done : .plain } func resetCellLongpressGesture(_ editing: Bool) { @@ -275,12 +293,6 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco return indexPath } - func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - guard let bookmark = bookmarksFRC?.object(at: indexPath) else { return false } - - return !bookmark.isFavorite - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: false) @@ -289,7 +301,7 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco if !bookmark.isFolder { if tableView.isEditing { //show editing view for bookmark item - self.showEditBookmarkController(tableView, indexPath: indexPath) + self.showEditBookmarkController(bookmark: bookmark) } else { if let url = URL(string: bookmark.url ?? "") { dismiss(animated: true) { @@ -300,9 +312,9 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco } else { if tableView.isEditing { //show editing view for bookmark item - self.showEditBookmarkController(tableView, indexPath: indexPath) + self.showEditBookmarkController(bookmark: bookmark) } else { - let nextController = BookmarksViewController(folder: bookmark, isPrivateBrowsing: isPrivateBrowsing) + let nextController = BookmarksViewController(mode: .bookmarks(inFolder: bookmark), isPrivateBrowsing: isPrivateBrowsing) nextController.profile = profile nextController.bookmarksDidChange = bookmarksDidChange nextController.toolbarUrlActionsDelegate = toolbarUrlActionsDelegate @@ -353,23 +365,29 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco }) let editAction = UITableViewRowAction(style: UITableViewRowAction.Style.normal, title: Strings.Edit, handler: { (action, indexPath) in - self.showEditBookmarkController(tableView, indexPath: indexPath) + self.showEditBookmarkController(bookmark: item) }) return [deleteAction, editAction] } - fileprivate func showEditBookmarkController(_ tableView: UITableView, indexPath: IndexPath) { - guard let item = bookmarksFRC?.object(at: indexPath), !item.isFavorite else { return } - - self.isEditingIndividualBookmark = true - - let mode: BookmarkEditMode = item.isFolder ? .editFolder(item) : .editBookmark(item) + fileprivate func showEditBookmarkController(bookmark: Bookmark) { + self.isEditingIndividualBookmark = true - let vc = AddEditBookmarkTableViewController(mode: mode) - self.navigationController?.pushViewController(vc, animated: true) - } - + var mode: BookmarkEditMode? + if bookmark.isFolder { + mode = .editFolder(bookmark) + } else if bookmark.isFavorite { + mode = .editFavorite(bookmark) + } else { + mode = .editBookmark(bookmark) + } + + if let mode = mode { + let vc = AddEditBookmarkTableViewController(mode: mode) + self.navigationController?.pushViewController(vc, animated: true) + } + } } extension BookmarksViewController: NSFetchedResultsControllerDelegate { diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift index 831e8466bfe..79f0726e5ab 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift @@ -218,7 +218,7 @@ class MenuViewController: UITableViewController { } private func openBookmarks() { - let vc = BookmarksViewController(folder: nil, isPrivateBrowsing: PrivateBrowsingManager.shared.isPrivateBrowsing) + let vc = BookmarksViewController(mode: .bookmarks(inFolder: nil), isPrivateBrowsing: PrivateBrowsingManager.shared.isPrivateBrowsing) vc.toolbarUrlActionsDelegate = bvc open(vc, doneButton: DoneButton(style: .done, position: .right)) diff --git a/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift b/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift index e20364b5354..13e965c765f 100644 --- a/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift +++ b/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift @@ -27,11 +27,17 @@ class ToolbarHelper: NSObject { toolbar.shareButton.accessibilityLabel = Strings.TabToolbarShareButtonAccessibilityLabel toolbar.shareButton.addTarget(self, action: #selector(didClickShare), for: UIControl.Event.touchUpInside) - toolbar.addTabButton.setImage(#imageLiteral(resourceName: "add_tab"), for: .normal) + toolbar.addTabButton.setImage(#imageLiteral(resourceName: "add_tab").template, for: .normal) toolbar.addTabButton.accessibilityLabel = Strings.TabToolbarAddTabButtonAccessibilityLabel toolbar.addTabButton.addTarget(self, action: #selector(didClickAddTab), for: UIControl.Event.touchUpInside) toolbar.addTabButton.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(didLongPressAddTab(_:)))) + toolbar.searchButton.setImage(#imageLiteral(resourceName: "ntp-search").template, for: .normal) + // Accessibility label not needed, since overriden in the bottom tool bar class. + toolbar.searchButton.addTarget(self, action: #selector(didClickSearch), for: UIControl.Event.touchUpInside) + // Same long press gesture allows creating tab on NTP, esp private tab easily + toolbar.searchButton.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(didLongPressAddTab(_:)))) + toolbar.menuButton.setImage(#imageLiteral(resourceName: "menu_more").template, for: .normal) toolbar.menuButton.accessibilityLabel = Strings.TabToolbarMenuButtonAccessibilityLabel toolbar.menuButton.addTarget(self, action: #selector(didClickMenu), for: UIControl.Event.touchUpInside) @@ -83,6 +89,10 @@ class ToolbarHelper: NSObject { toolbar.tabToolbarDelegate?.tabToolbarDidPressAddTab(toolbar, button: toolbar.shareButton) } + func didClickSearch() { + toolbar.tabToolbarDelegate?.tabToolbarDidPressSearch(toolbar, button: toolbar.searchButton) + } + func didLongPressAddTab(_ longPress: UILongPressGestureRecognizer) { if longPress.state == .began { toolbar.tabToolbarDelegate?.tabToolbarDidLongPressAddTab(toolbar, button: toolbar.shareButton) diff --git a/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift b/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift index 0e7cc14da1e..9caa05842e0 100644 --- a/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift +++ b/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift @@ -12,6 +12,7 @@ protocol ToolbarProtocol: class { var forwardButton: ToolbarButton { get } var shareButton: ToolbarButton { get } var addTabButton: ToolbarButton { get } + var searchButton: ToolbarButton { get } var menuButton: ToolbarButton { get } var actionButtons: [Themeable & UIButton] { get } @@ -48,6 +49,7 @@ protocol ToolbarDelegate: class { func tabToolbarDidLongPressTabs(_ tabToolbar: ToolbarProtocol, button: UIButton) func tabToolbarDidPressShare() func tabToolbarDidPressAddTab(_ tabToolbar: ToolbarProtocol, button: UIButton) + func tabToolbarDidPressSearch(_ tabToolbar: ToolbarProtocol, button: UIButton) func tabToolbarDidLongPressAddTab(_ tabToolbar: ToolbarProtocol, button: UIButton) func tabToolbarDidSwipeToChangeTabs(_ tabToolbar: ToolbarProtocol, direction: UISwipeGestureRecognizer.Direction) } diff --git a/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift b/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift index b1423afc350..2a7a4791c32 100644 --- a/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift +++ b/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift @@ -37,7 +37,7 @@ protocol TopToolbarDelegate: class { // Returns either (search query, true) or (url, false). func topToolbarDisplayTextForURL(_ url: URL?) -> (String?, Bool) func topToolbarDidBeginDragInteraction(_ topToolbar: TopToolbarView) - func topToolbarDidTapBookmarkButton(_ topToolbar: TopToolbarView) + func topToolbarDidTapBookmarkButton(_ topToolbar: TopToolbarView?, favorites: Bool) func topToolbarDidTapBraveShieldsButton(_ topToolbar: TopToolbarView) func topToolbarDidTapBraveRewardsButton(_ topToolbar: TopToolbarView) func topToolbarDidTapMenuButton(_ topToolbar: TopToolbarView) @@ -145,6 +145,8 @@ class TopToolbarView: UIView, ToolbarProtocol { var forwardButton = ToolbarButton(top: true) var shareButton = ToolbarButton(top: true) var addTabButton = ToolbarButton(top: true) + // Do nothing with this, just required for protocol conformance + var searchButton = ToolbarButton(top: true) lazy var menuButton = ToolbarButton(top: true).then { $0.contentMode = .center $0.accessibilityIdentifier = "topToolbarView-menuButton" @@ -470,7 +472,7 @@ class TopToolbarView: UIView, ToolbarProtocol { } @objc func didClickBookmarkButton() { - delegate?.topToolbarDidTapBookmarkButton(self) + delegate?.topToolbarDidTapBookmarkButton(self, favorites: false) } @objc func didClickMenu() { diff --git a/Client/Frontend/Settings/NewTabPageTableViewController.swift b/Client/Frontend/Settings/NewTabPageTableViewController.swift new file mode 100644 index 00000000000..c2bfd18394a --- /dev/null +++ b/Client/Frontend/Settings/NewTabPageTableViewController.swift @@ -0,0 +1,59 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import Static +import BraveShared +import Shared + +class NewTabPageTableViewController: TableViewController { + let sponsoredRow = BoolRow(title: Strings.NewTabPageSettingsSponsoredImages, option: Preferences.NewTabPage.backgroundSponsoredImages) + + override func viewDidLoad() { + super.viewDidLoad() + + // Hides unnecessary empty rows + tableView.tableFooterView = UIView() + + navigationItem.title = Strings.NewTabPageSettingsTitle + tableView.accessibilityIdentifier = "NewTabPageSettings.tableView" + dataSource.sections = [section] + + // Sponsored switch is only enabled if the background images is enabled also. + self.sponsoredSwitch?.isEnabled = Preferences.NewTabPage.backgroundImages.value + } + + private lazy var section: Section = { + var rows = [ + BoolRow(title: Strings.NewTabPageSettingsBackgroundImages, option: Preferences.NewTabPage.backgroundImages, onValueChange: { + newValue in + // Since overriding, does not auto-adjust this setting. + Preferences.NewTabPage.backgroundImages.value = newValue + + // If turning off normal background images, turn of sponsored images as well. + Preferences.NewTabPage.backgroundSponsoredImages.value = newValue + + if !newValue { + // Updating the underlying preference does not dynamically update the visuals unfortuantely. + // Updating manually. Only update if disabling. + self.sponsoredSwitch?.isOn = newValue + } + + // Need to update every time. + self.sponsoredSwitch?.isEnabled = newValue + }) + ] + + if BackgroundImage.showSponsoredSetting { + rows.append(sponsoredRow) + } + + rows.append(BoolRow(title: Strings.NewTabPageSettingsAutoOpenKeyboard, option: Preferences.NewTabPage.autoOpenKeyboard)) + return Section(rows: rows) + }() + + private var sponsoredSwitch: UISwitch? { + return sponsoredRow.accessory.view as? SwitchAccessoryView + } +} diff --git a/Client/Frontend/Settings/SettingsViewController.swift b/Client/Frontend/Settings/SettingsViewController.swift index c703a7b3cc0..2d0eee7a70e 100644 --- a/Client/Frontend/Settings/SettingsViewController.swift +++ b/Client/Frontend/Settings/SettingsViewController.swift @@ -159,6 +159,15 @@ class SettingsViewController: TableViewController { } display.rows.append(row) + row = Row(text: Strings.NewTabPageSettingsTitle, + selection: { [unowned self] in + self.navigationController?.pushViewController(NewTabPageTableViewController(), animated: true) + }, + accessory: .disclosureIndicator, + cellClass: MultilineValue1Cell.self + ) + display.rows.append(row) + if UIDevice.current.userInterfaceIdiom == .pad { display.rows.append( Row(text: Strings.Show_Tabs_Bar, accessory: .switchToggle(value: Preferences.General.tabBarVisibility.value == TabBarVisibility.always.rawValue, { Preferences.General.tabBarVisibility.value = $0 ? TabBarVisibility.always.rawValue : TabBarVisibility.never.rawValue }), cellClass: MultilineValue1Cell.self) diff --git a/Client/Frontend/Sync/SyncViewController.swift b/Client/Frontend/Sync/SyncViewController.swift index 1f36e13b1e1..57d839a0434 100644 --- a/Client/Frontend/Sync/SyncViewController.swift +++ b/Client/Frontend/Sync/SyncViewController.swift @@ -12,6 +12,13 @@ class RoundInterfaceButton: UIButton { } } +class RoundInterfaceView: UIView { + override func layoutSubviews() { + super.layoutSubviews() + layer.cornerRadius = min(bounds.height, bounds.width) / 2.0 + } +} + class SyncViewController: UIViewController { override func loadView() { diff --git a/Client/Frontend/UX.swift b/Client/Frontend/UX.swift index b1b8e112825..c0bac8dacc5 100644 --- a/Client/Frontend/UX.swift +++ b/Client/Frontend/UX.swift @@ -23,12 +23,7 @@ struct UX { static let BackgroundColor = UIColor.white static let StatTitleColor = UIColor(rgb: 0x8C9094) } - - struct Favorites { - static let cellLabelColorNormal = UIColor(rgb: 0x2D2D2D) - static let cellLabelColorPrivate = UIColor(rgb: 0xDBDBDB) - } - + static let BraveOrange = UIColor(rgb: 0xfb542b) static let Blue = UIColor(rgb: 0x424acb) diff --git a/ClientTests/FavoritesViewControllerTests.swift b/ClientTests/FavoritesViewControllerTests.swift index 11f8c5b98f5..583f46232c6 100644 --- a/ClientTests/FavoritesViewControllerTests.swift +++ b/ClientTests/FavoritesViewControllerTests.swift @@ -141,4 +141,8 @@ class MockTopSitesDelegate: TopSitesDelegate { guard let input = self.input else { return false } return URL(string: input) != nil } + + func didTapShowMoreFavorites() { + // Protocol conformance + } }