diff --git a/UITabBrowser.xcodeproj/project.pbxproj b/UITabBrowser.xcodeproj/project.pbxproj index 818a29d..b6dcfe2 100644 --- a/UITabBrowser.xcodeproj/project.pbxproj +++ b/UITabBrowser.xcodeproj/project.pbxproj @@ -10,14 +10,16 @@ A9014E3625C1B37700866F3A /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9014E3525C1B37700866F3A /* Browser.swift */; }; A91E73A825E7F73C00418F1E /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91E73A625E7F73C00418F1E /* OnboardingViewController.swift */; }; A91E73B425E8030200418F1E /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91E73B325E8030200418F1E /* SafariView.swift */; }; - A91F142E25E938F700651645 /* Onboarding5ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91F142D25E938F700651645 /* Onboarding5ViewController.swift */; }; - A91F143325E93D6C00651645 /* Onboarding1ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91F143225E93D6C00651645 /* Onboarding1ViewController.swift */; }; + A91F142E25E938F700651645 /* OnboardingLastViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91F142D25E938F700651645 /* OnboardingLastViewController.swift */; }; + A91F143325E93D6C00651645 /* OnboardingFirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91F143225E93D6C00651645 /* OnboardingFirstViewController.swift */; }; A91F42BE25C830A800C2FCC2 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91F42BD25C830A800C2FCC2 /* MainViewModel.swift */; }; A92A8B7325BB142100BA66FD /* TabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92A8B7225BB142100BA66FD /* TabsViewModel.swift */; }; - A936CD2825E90F2100522C7E /* seaerchbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = A936CD2725E90F2100522C7E /* seaerchbutton.png */; }; + A930CA422604C7FD00127114 /* searchbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = A930CA412604C7FD00127114 /* searchbutton.png */; }; + A930CA4B2604CAF900127114 /* private-mode.png in Resources */ = {isa = PBXBuildFile; fileRef = A930CA4D2604CAF900127114 /* private-mode.png */; }; A936CD3325E914C700522C7E /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A936CD3525E914C700522C7E /* Onboarding.storyboard */; }; A936CD4025E9157100522C7E /* contextmenu.png in Resources */ = {isa = PBXBuildFile; fileRef = A936CD4225E9157100522C7E /* contextmenu.png */; }; A9401C2525C2D86000AEAF74 /* TabCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9401C2425C2D86000AEAF74 /* TabCell.swift */; }; + A9472BD92600E22500D37423 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9472BD82600E22500D37423 /* Colors.xcassets */; }; A94C246925D54B6C00AC0E0A /* BookmarkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94C246825D54B6C00AC0E0A /* BookmarkViewModel.swift */; }; A9519BD625E93FCB00AAF25C /* launchicon.png in Resources */ = {isa = PBXBuildFile; fileRef = A9519BD525E93FCB00AAF25C /* launchicon.png */; }; A95E516325DBE45B00BD36EC /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E516225DBE45B00BD36EC /* Settings.swift */; }; @@ -79,16 +81,19 @@ A9014E3525C1B37700866F3A /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = ""; }; A91E73A625E7F73C00418F1E /* OnboardingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; A91E73B325E8030200418F1E /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; - A91F142D25E938F700651645 /* Onboarding5ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding5ViewController.swift; sourceTree = ""; }; - A91F143225E93D6C00651645 /* Onboarding1ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Onboarding1ViewController.swift; sourceTree = ""; }; + A91F142D25E938F700651645 /* OnboardingLastViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingLastViewController.swift; sourceTree = ""; }; + A91F143225E93D6C00651645 /* OnboardingFirstViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingFirstViewController.swift; sourceTree = ""; }; A91F42BD25C830A800C2FCC2 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; A92A8B7225BB142100BA66FD /* TabsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsViewModel.swift; sourceTree = ""; }; - A936CD2725E90F2100522C7E /* seaerchbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = seaerchbutton.png; sourceTree = ""; }; + A930CA412604C7FD00127114 /* searchbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = searchbutton.png; sourceTree = ""; }; + A930CA4C2604CAF900127114 /* ja */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ja; path = "ja.lproj/private-mode.png"; sourceTree = ""; }; + A930CA512604CB0400127114 /* Base */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Base; path = "Base.lproj/private-mode.png"; sourceTree = ""; }; A936CD3425E914C700522C7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Onboarding.storyboard; sourceTree = ""; }; A936CD3A25E914CB00522C7E /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Onboarding.strings; sourceTree = ""; }; A936CD4125E9157100522C7E /* ja */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ja; path = ja.lproj/contextmenu.png; sourceTree = ""; }; A936CD4625E9157A00522C7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Base; path = Base.lproj/contextmenu.png; sourceTree = ""; }; A9401C2425C2D86000AEAF74 /* TabCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabCell.swift; sourceTree = ""; }; + A9472BD82600E22500D37423 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; A94C246825D54B6C00AC0E0A /* BookmarkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = ""; }; A9519BD525E93FCB00AAF25C /* launchicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = launchicon.png; sourceTree = ""; }; A95E516225DBE45B00BD36EC /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; @@ -181,8 +186,8 @@ isa = PBXGroup; children = ( A91E73A625E7F73C00418F1E /* OnboardingViewController.swift */, - A91F143225E93D6C00651645 /* Onboarding1ViewController.swift */, - A91F142D25E938F700651645 /* Onboarding5ViewController.swift */, + A91F143225E93D6C00651645 /* OnboardingFirstViewController.swift */, + A91F142D25E938F700651645 /* OnboardingLastViewController.swift */, A936CD3525E914C700522C7E /* Onboarding.storyboard */, ); name = Onboarding; @@ -211,10 +216,12 @@ isa = PBXGroup; children = ( A984063A2598C909006B6F49 /* Assets.xcassets */, + A9472BD82600E22500D37423 /* Colors.xcassets */, A9519BD525E93FCB00AAF25C /* launchicon.png */, A9D7BE5C25E91DE200AB5D53 /* buttons_dark.png */, A9D7BE5D25E91DE200AB5D53 /* buttons_light.png */, - A936CD2725E90F2100522C7E /* seaerchbutton.png */, + A930CA412604C7FD00127114 /* searchbutton.png */, + A930CA4D2604CAF900127114 /* private-mode.png */, A9AB218325E92E8A001454CA /* closebutton.png */, A936CD4225E9157100522C7E /* contextmenu.png */, ); @@ -488,12 +495,14 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A930CA4B2604CAF900127114 /* private-mode.png in Resources */, A98A10AA25E6864300431B6B /* Bookmark.storyboard in Resources */, A9519BD625E93FCB00AAF25C /* launchicon.png in Resources */, - A936CD2825E90F2100522C7E /* seaerchbutton.png in Resources */, + A9472BD92600E22500D37423 /* Colors.xcassets in Resources */, A98A10B925E6875C00431B6B /* LaunchScreen.storyboard in Resources */, A984063B2598C909006B6F49 /* Assets.xcassets in Resources */, A9AB218125E92E8A001454CA /* closebutton.png in Resources */, + A930CA422604C7FD00127114 /* searchbutton.png in Resources */, A98406392598C908006B6F49 /* Main.storyboard in Resources */, A98A10CE25E6892300431B6B /* Localizable.strings in Resources */, A936CD3325E914C700522C7E /* Onboarding.storyboard in Resources */, @@ -532,7 +541,7 @@ A9D014A025C52D8E00A59C9D /* ImageLoader.swift in Sources */, A99B90DA25BAAD4C00554B1D /* TabsViewController.swift in Sources */, A9DD2C5125CEFD2A000771ED /* String+URL.swift in Sources */, - A91F142E25E938F700651645 /* Onboarding5ViewController.swift in Sources */, + A91F142E25E938F700651645 /* OnboardingLastViewController.swift in Sources */, A9D6440025DAC1920017D79D /* SearchEngineViewController.swift in Sources */, A9401C2525C2D86000AEAF74 /* TabCell.swift in Sources */, A98406362598C908006B6F49 /* BrowsersViewController.swift in Sources */, @@ -541,7 +550,7 @@ A98406342598C908006B6F49 /* SceneDelegate.swift in Sources */, A99DC45025CD731D0095FAD7 /* SearchBarViewModel.swift in Sources */, A9A12D9925BAECAA008843FF /* Tab.swift in Sources */, - A91F143325E93D6C00651645 /* Onboarding1ViewController.swift in Sources */, + A91F143325E93D6C00651645 /* OnboardingFirstViewController.swift in Sources */, A97F983125DEB83E007D978C /* CollectionType.swift in Sources */, A973006725F66E9000C85C73 /* KeyboardBarViewController.swift in Sources */, A98406652598CF1E006B6F49 /* WebViewController.swift in Sources */, @@ -594,6 +603,15 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + A930CA4D2604CAF900127114 /* private-mode.png */ = { + isa = PBXVariantGroup; + children = ( + A930CA4C2604CAF900127114 /* ja */, + A930CA512604CB0400127114 /* Base */, + ); + name = "private-mode.png"; + sourceTree = ""; + }; A936CD3525E914C700522C7E /* Onboarding.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -783,14 +801,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = UITabBrowser/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.UITabBrowser; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -804,14 +822,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = UITabBrowser/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.UITabBrowser; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; diff --git a/UITabBrowser/Assets/Base.lproj/closebutton.png b/UITabBrowser/Assets/Base.lproj/closebutton.png index ca70540..7ebe453 100644 Binary files a/UITabBrowser/Assets/Base.lproj/closebutton.png and b/UITabBrowser/Assets/Base.lproj/closebutton.png differ diff --git a/UITabBrowser/Assets/Base.lproj/contextmenu.png b/UITabBrowser/Assets/Base.lproj/contextmenu.png index 73cb5ff..f7a663d 100644 Binary files a/UITabBrowser/Assets/Base.lproj/contextmenu.png and b/UITabBrowser/Assets/Base.lproj/contextmenu.png differ diff --git a/UITabBrowser/Assets/Base.lproj/private-mode.png b/UITabBrowser/Assets/Base.lproj/private-mode.png new file mode 100644 index 0000000..81950da Binary files /dev/null and b/UITabBrowser/Assets/Base.lproj/private-mode.png differ diff --git a/UITabBrowser/Assets/Colors.xcassets/Contents.json b/UITabBrowser/Assets/Colors.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/UITabBrowser/Assets/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UITabBrowser/Assets/Colors.xcassets/SearchBarBackgroundNormalMode.colorset/Contents.json b/UITabBrowser/Assets/Colors.xcassets/SearchBarBackgroundNormalMode.colorset/Contents.json new file mode 100644 index 0000000..9a2b6c8 --- /dev/null +++ b/UITabBrowser/Assets/Colors.xcassets/SearchBarBackgroundNormalMode.colorset/Contents.json @@ -0,0 +1,28 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "ios", + "reference" : "systemGray5Color" + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "ios", + "reference" : "systemGray5Color" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UITabBrowser/Assets/Colors.xcassets/SearchBarBackgroundPrivateMode.colorset/Contents.json b/UITabBrowser/Assets/Colors.xcassets/SearchBarBackgroundPrivateMode.colorset/Contents.json new file mode 100644 index 0000000..5c70549 --- /dev/null +++ b/UITabBrowser/Assets/Colors.xcassets/SearchBarBackgroundPrivateMode.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x44", + "green" : "0x44", + "red" : "0x44" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x04", + "green" : "0x04", + "red" : "0x04" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UITabBrowser/Assets/Colors.xcassets/SearchBarTextNormalMode.colorset/Contents.json b/UITabBrowser/Assets/Colors.xcassets/SearchBarTextNormalMode.colorset/Contents.json new file mode 100644 index 0000000..b660415 --- /dev/null +++ b/UITabBrowser/Assets/Colors.xcassets/SearchBarTextNormalMode.colorset/Contents.json @@ -0,0 +1,28 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "ios", + "reference" : "labelColor" + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "ios", + "reference" : "labelColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UITabBrowser/Assets/Colors.xcassets/SearchBarTextPrivateMode.colorset/Contents.json b/UITabBrowser/Assets/Colors.xcassets/SearchBarTextPrivateMode.colorset/Contents.json new file mode 100644 index 0000000..f36202b --- /dev/null +++ b/UITabBrowser/Assets/Colors.xcassets/SearchBarTextPrivateMode.colorset/Contents.json @@ -0,0 +1,28 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "ios", + "reference" : "systemBackgroundColor" + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "ios", + "reference" : "labelColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UITabBrowser/Assets/ja.lproj/closebutton.png b/UITabBrowser/Assets/ja.lproj/closebutton.png index b3082ee..0f41069 100644 Binary files a/UITabBrowser/Assets/ja.lproj/closebutton.png and b/UITabBrowser/Assets/ja.lproj/closebutton.png differ diff --git a/UITabBrowser/Assets/ja.lproj/contextmenu.png b/UITabBrowser/Assets/ja.lproj/contextmenu.png index 6930e8f..020e9c4 100644 Binary files a/UITabBrowser/Assets/ja.lproj/contextmenu.png and b/UITabBrowser/Assets/ja.lproj/contextmenu.png differ diff --git a/UITabBrowser/Assets/ja.lproj/private-mode.png b/UITabBrowser/Assets/ja.lproj/private-mode.png new file mode 100644 index 0000000..050112f Binary files /dev/null and b/UITabBrowser/Assets/ja.lproj/private-mode.png differ diff --git a/UITabBrowser/Assets/searchbutton.png b/UITabBrowser/Assets/searchbutton.png new file mode 100644 index 0000000..8748aab Binary files /dev/null and b/UITabBrowser/Assets/searchbutton.png differ diff --git a/UITabBrowser/Browsers/WebViewController.swift b/UITabBrowser/Browsers/WebViewController.swift index 7972bc1..a2196ca 100644 --- a/UITabBrowser/Browsers/WebViewController.swift +++ b/UITabBrowser/Browsers/WebViewController.swift @@ -54,11 +54,14 @@ class WebViewController: UIViewController { var webView: WKWebView! = nil weak var delegate: WebViewControllerDelegate? - func load(url: URL) { + func load(url: URL, privateMode: Bool = false) { // instance if webView == nil { let config = WKWebViewConfiguration() + if privateMode { + config.websiteDataStore = .nonPersistent() + } webView = WKWebView(frame: .zero, configuration: config) webView.uiDelegate = self webView.navigationDelegate = self diff --git a/UITabBrowser/Main/MainViewController.swift b/UITabBrowser/Main/MainViewController.swift index f7c9ebf..dd2c1fe 100644 --- a/UITabBrowser/Main/MainViewController.swift +++ b/UITabBrowser/Main/MainViewController.swift @@ -35,11 +35,7 @@ class MainViewController: UIViewController { // MARK: - Actions @IBAction func showSearch(_ sender: Any) { - if viewModel.isSearchView { - searchBar.becomeFirstResponder() - } else { - viewModel.showSearch() - } + self.showSearch(privateMode: false) } @IBAction func goBack(_ sender: Any) { @@ -140,6 +136,19 @@ class MainViewController: UIViewController { // MARK: - Close menu extension MainViewController { + + private func showSearch(privateMode: Bool) { + if viewModel.isSearchView { + let sharedBrowsers = Browsers.shared + if let id = sharedBrowsers.currentBrowser?.id { + sharedBrowsers.setPrivateMode(id: id, mode: privateMode) + } + searchBar.becomeFirstResponder() + } else { + viewModel.showSearch(privateMode: privateMode) + } + } + func configureCloseMenu() { let actions = [ UIAction( @@ -153,6 +162,20 @@ extension MainViewController { self.viewModel.close() } ), + UIAction( + title: NSLocalizedString( + "Close All Private Tabs", + comment: "in Context Menu of Close button" + ), + image: UIImage(systemName: "xmark.shield.fill"), + identifier: nil, + discoverabilityTitle: nil, + attributes: [], + state: .off, + handler: { action in + self.viewModel.closeAllPrivateTabs() + } + ), UIAction( title: NSLocalizedString("Close All", comment: "in Context Menu of Close button"), image: UIImage(systemName: "clear.fill"), @@ -184,7 +207,21 @@ extension MainViewController { attributes: [], state: .off, handler: { _ in - self.showSearch(self) + self.showSearch(privateMode: false) + } + ), + UIAction( + title: NSLocalizedString( + "Private Search", + comment: "in Context Menu of Search button" + ), + image: UIImage(systemName: "shield.lefthalf.fill"), + identifier: nil, + discoverabilityTitle: nil, + attributes: [], + state: .off, + handler: { _ in + self.showSearch(privateMode: true) } ), UIAction( diff --git a/UITabBrowser/Main/MainViewModel.swift b/UITabBrowser/Main/MainViewModel.swift index 6fd58f4..70496b6 100644 --- a/UITabBrowser/Main/MainViewModel.swift +++ b/UITabBrowser/Main/MainViewModel.swift @@ -99,12 +99,19 @@ class MainViewModel: NSObject, ObservableObject { } } + // Close all tabs func closeAll() { browsers.deleteAll() } - func showSearch() { - browsers.showSearch() + // Close all private tabs + func closeAllPrivateTabs() { + browsers.deleteAllPrivate() + } + + // Show search with private flag + func showSearch(privateMode: Bool = false) { + browsers.showSearch(privateMode: privateMode) } func searchFromClipboard() { diff --git a/UITabBrowser/Model/Browser.swift b/UITabBrowser/Model/Browser.swift index d93f8e0..9e63125 100644 --- a/UITabBrowser/Model/Browser.swift +++ b/UITabBrowser/Model/Browser.swift @@ -41,6 +41,7 @@ class Browser { var type: PageType var title = "" var pinned = false + var privateMode = false var loading = false var progress: Float = 0.0 var canGoBack = false @@ -70,11 +71,13 @@ class Browser { urlString: String? = nil, title: String = "", selected: Bool = false, - pinned: Bool = false + pinned: Bool = false, + privateMode: Bool = false ) { self.type = type self.selected = selected self.pinned = pinned + self.privateMode = privateMode self.title = title switch type { case .browser: @@ -125,7 +128,10 @@ class Browser { .receive(on: DispatchQueue.main) .sink { [weak self] url in // Load contents - (self?.viewController as! WebViewController).load(url: url) + (self?.viewController as! WebViewController).load( + url: url, + privateMode: privateMode + ) // Set flag self?.isContentLoaded = true } @@ -144,7 +150,7 @@ class Browser { if let vc = self.viewController { switch vc { case is WebViewController: - (vc as! WebViewController).load(url: url) + (vc as! WebViewController).load(url: url, privateMode: privateMode) case is SearchResultsController: // Replace with WebView self.viewController.view.isHidden = true @@ -154,7 +160,7 @@ class Browser { self.url = url let webVC = WebViewController() webVC.delegate = self - webVC.load(url: url) + webVC.load(url: url, privateMode: privateMode) self.viewController = webVC default: fatalError(vc.debugDescription) @@ -216,7 +222,7 @@ extension Browser: WebViewControllerDelegate { } func webViewController(_ viewController: WebViewController, openNewTab url: URL) { - Browsers.shared.insertBrowser(urlString: url.absoluteString) + Browsers.shared.insertBrowser(urlString: url.absoluteString, privateMode: privateMode) } func webViewController(_ viewController: WebViewController, didScroll offset: CGPoint) { @@ -230,7 +236,9 @@ extension Browser: WebViewControllerDelegate { func webViewControllerDidFinishNavigation(_ viewController: WebViewController) { // Save history - if let webView = viewController.webView, let url = webView.url, let title = webView.title { + if !privateMode, let webView = viewController.webView, let url = webView.url, + let title = webView.title + { Items.shared.add( type: .history, title: title, diff --git a/UITabBrowser/Model/Browsers.swift b/UITabBrowser/Model/Browsers.swift index 8c113e0..31712c7 100644 --- a/UITabBrowser/Model/Browsers.swift +++ b/UITabBrowser/Model/Browsers.swift @@ -105,15 +105,16 @@ final class Browsers: NSObject, ObservableObject { updateCurrentBrowser() } - func appendSearch() { + func appendSearch(privateMode: Bool = false) { let browser = Browser(type: .search) browser.delegate = self + browser.privateMode = privateMode browsers.append(browser) selectLast() } - func insertBrowser(urlString: String) { - let browser = Browser(type: .browser, urlString: urlString) + func insertBrowser(urlString: String, privateMode: Bool = false) { + let browser = Browser(type: .browser, urlString: urlString, privateMode: privateMode) browser.delegate = self browsers.insert(browser, at: selectedIndex + 1) select(index: selectedIndex + 1) @@ -169,6 +170,33 @@ final class Browsers: NSObject, ObservableObject { } } + func deleteAllPrivate() { + // Save current browser to keep it as much as possible + let currentId = browsers.find(where: { $0.selected })?.id + // Filter + browsers = browsers.filter { !$0.privateMode } + // Show search view if no browser exists + if browsers.count == 0 { + appendSearch() + } + // Select tab + if let newBrowser = browsers.find( + where: { + currentId != nil && $0.id == currentId + } + ) { + select(id: newBrowser.id) + } else { + selectLast() + } + } + + /* + func hasPrivate() -> Bool { + return browsers.firstIndex { $0.privateMode } != nil + } + */ + func getSearchViewID() -> BrowserID? { if let index = browsers.firstIndex(where: { $0.type == .search }) { return browsers[index].id @@ -177,13 +205,14 @@ final class Browsers: NSObject, ObservableObject { } } - func showSearch() { + func showSearch(privateMode: Bool = false) { if let id = getSearchViewID() { // move to search select(id: id) + setPrivateMode(id: id, mode: privateMode) } else { // Add search - appendSearch() + appendSearch(privateMode: privateMode) } } @@ -226,6 +255,17 @@ final class Browsers: NSObject, ObservableObject { } } + // private mode + func setPrivateMode(id: BrowserID, mode: Bool = true) { + browsers = browsers.map({ browser in + if browser.id == id { + browser.privateMode = mode + } + return browser + }) + updateCurrentBrowser() + } + func get(id: BrowserID) -> Browser? { return browsers.find { $0.id == id } } diff --git a/UITabBrowser/Model/Settings.swift b/UITabBrowser/Model/Settings.swift index 3a4c849..bc722a8 100644 --- a/UITabBrowser/Model/Settings.swift +++ b/UITabBrowser/Model/Settings.swift @@ -84,16 +84,18 @@ final class Settings { .enumerated() .forEach { let browser = $0.element - items.add( - type: .tab, - title: browser.title, - url: browser.url!, - keywords: "", - browserId: browser.id, - selected: browser.selected, - pinned: browser.pinned, - order: $0.offset - ) + if !browser.privateMode { + items.add( + type: .tab, + title: browser.title, + url: browser.url!, + keywords: "", + browserId: browser.id, + selected: browser.selected, + pinned: browser.pinned, + order: $0.offset + ) + } } } } diff --git a/UITabBrowser/Model/Tab.swift b/UITabBrowser/Model/Tab.swift index 2378000..0c2cedc 100644 --- a/UITabBrowser/Model/Tab.swift +++ b/UITabBrowser/Model/Tab.swift @@ -7,7 +7,6 @@ import UIKit -// TODO: Consider if it should have reference of browser struct Tab: Hashable { let id: BrowserID var type: PageType @@ -17,4 +16,5 @@ struct Tab: Hashable { var active: Bool var loading: Bool var pinned: Bool + var privateMode: Bool } diff --git a/UITabBrowser/Onboarding/Base.lproj/Onboarding.storyboard b/UITabBrowser/Onboarding/Base.lproj/Onboarding.storyboard index c8c1388..3200ae8 100644 --- a/UITabBrowser/Onboarding/Base.lproj/Onboarding.storyboard +++ b/UITabBrowser/Onboarding/Base.lproj/Onboarding.storyboard @@ -96,10 +96,10 @@ - + - + @@ -166,7 +166,7 @@ VGFwIHRoZSBtYWduaWZpZXIgYnV0dG9uERERERENaWYgeW91IHdhbnQgdG8gc2VhcmNoA - + @@ -204,7 +204,7 @@ VGFwIHRoZSBtYWduaWZpZXIgYnV0dG9uERERERENaWYgeW91IHdhbnQgdG8gc2VhcmNoA - + @@ -233,10 +233,10 @@ VGFwIHRoZSBtYWduaWZpZXIgYnV0dG9uERERERENaWYgeW91IHdhbnQgdG8gc2VhcmNoA - + - + @@ -303,14 +303,54 @@ b3NlIGFsbCB0YWJzA - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + diff --git a/UITabBrowser/Onboarding/OnboardingFirstViewController.swift b/UITabBrowser/Onboarding/OnboardingFirstViewController.swift new file mode 100644 index 0000000..447ea15 --- /dev/null +++ b/UITabBrowser/Onboarding/OnboardingFirstViewController.swift @@ -0,0 +1,15 @@ +// +// OnboardingFirstViewController.swift +// UITabBrowser +// +// Created by ogaoga on 2021/02/26. +// + +import UIKit + +class OnboardingFirstViewController: UIViewController, UISearchBarDelegate { + + @IBAction func privacyPolicy(_ sender: Any) { + showPrivacyPolicy(parent: self) + } +} diff --git a/UITabBrowser/Onboarding/OnboardingLastViewController.swift b/UITabBrowser/Onboarding/OnboardingLastViewController.swift new file mode 100644 index 0000000..08446fc --- /dev/null +++ b/UITabBrowser/Onboarding/OnboardingLastViewController.swift @@ -0,0 +1,16 @@ +// +// OnboardingLastViewController.swift +// UITabBrowser +// +// Created by ogaoga on 2021/02/26. +// + +import UIKit + +class OnboardingLastViewController: UIViewController, UISearchBarDelegate { + + func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { + dismiss(animated: true) + return false + } +} diff --git a/UITabBrowser/Onboarding/OnboardingViewController.swift b/UITabBrowser/Onboarding/OnboardingViewController.swift index b8a0790..7dda328 100644 --- a/UITabBrowser/Onboarding/OnboardingViewController.swift +++ b/UITabBrowser/Onboarding/OnboardingViewController.swift @@ -13,6 +13,14 @@ class OnboardingViewController: UIViewController { private var pages: [UIViewController] = [] private var pageViewController: UIPageViewController! = nil private var cancellables: Set = [] + private let pageNames = [ + "View1", + "View2", + "ViewPrivateBrowse", + "View3", + "View4", + "View5", + ] @Published private var index = 0 @@ -102,8 +110,8 @@ class OnboardingViewController: UIViewController { // Add view controllers let storyboard = UIStoryboard(name: "Onboarding", bundle: nil) - pages = (1...5).map { - storyboard.instantiateViewController(withIdentifier: "View\($0)") + pages = pageNames.map { + storyboard.instantiateViewController(withIdentifier: $0) } pageViewController.setViewControllers( [pages[0]], diff --git a/UITabBrowser/Onboarding/ja.lproj/Onboarding.strings b/UITabBrowser/Onboarding/ja.lproj/Onboarding.strings index 5cf07ec..9bbcd80 100644 --- a/UITabBrowser/Onboarding/ja.lproj/Onboarding.strings +++ b/UITabBrowser/Onboarding/ja.lproj/Onboarding.strings @@ -22,3 +22,5 @@ "CyJ-uN-jmU.text" = "検索キーワードか URL を入力して\nはじめましょう!"; "smS-e5-BXq.placeholder" = "検索キーワードか URL"; + +"FGp-Nq-9dG.text" = "虫眼鏡ボタンを長押しして、\nプライベートモードに。"; diff --git a/UITabBrowser/SearchBar/SearchBar.swift b/UITabBrowser/SearchBar/SearchBar.swift index 35b9c73..2132f76 100644 --- a/UITabBrowser/SearchBar/SearchBar.swift +++ b/UITabBrowser/SearchBar/SearchBar.swift @@ -10,6 +10,8 @@ import UIKit class SearchBar: UISearchBar { + typealias IconType = SearchBarViewModel.IconType + private let viewModel = SearchBarViewModel() private var cancellables: Set = [] @@ -43,38 +45,23 @@ class SearchBar: UISearchBar { .store(in: &cancellables) // Icon - viewModel.$url - .dropFirst() - .receive(on: DispatchQueue.main) - .compactMap { $0?.absoluteString } - .sink { urlString in - self.setImage( - UIImage( - systemName: urlString.isSecureURL ? "lock.fill" : "lock.slash" - ), - for: .search, - state: .normal - ) - } - .store(in: &cancellables) - viewModel.$isSearch + viewModel.$iconType + .removeDuplicates() .combineLatest($editing) - .dropFirst() + .combineLatest(viewModel.$privateMode) .receive(on: DispatchQueue.main) - .map { (isSearch, editing) -> String in - if isSearch || editing { - return "magnifyingglass" - } else { - if let isSecureURL = self.text?.isSecureURL, isSecureURL { - return "lock.fill" - } else { - return "lock.slash" - } - } - } - .sink { name in + .sink { + let (iconType, editing) = $0 + let privateMode = $1 self.setImage( - UIImage(systemName: name), + UIImage(systemName: editing ? IconType.Magnifyingglass.name : iconType.name)! + .withTintColor( + UIColor( + named: privateMode + ? "SearchBarTextPrivateMode" : "SearchBarTextNormalMode" + )!, + renderingMode: .alwaysOriginal + ), for: .search, state: .normal ) @@ -100,13 +87,26 @@ class SearchBar: UISearchBar { // Set keywords form others viewModel.$keywords .dropFirst() - .receive(on: DispatchQueue.main) .compactMap { $0 } + .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] text in self?.text = text self?.becomeFirstResponder() }) .store(in: &cancellables) + + viewModel.$privateMode + .receive(on: DispatchQueue.main) + .sink { [weak self] privateMode in + self?.searchTextField.backgroundColor = UIColor( + named: privateMode + ? "SearchBarBackgroundPrivateMode" : "SearchBarBackgroundNormalMode" + ) + self?.searchTextField.textColor = UIColor( + named: privateMode ? "SearchBarTextPrivateMode" : "SearchBarTextNormalMode" + ) + } + .store(in: &cancellables) } required init?(coder: NSCoder) { diff --git a/UITabBrowser/SearchBar/SearchBarViewModel.swift b/UITabBrowser/SearchBar/SearchBarViewModel.swift index a4b353b..8de7d7e 100644 --- a/UITabBrowser/SearchBar/SearchBarViewModel.swift +++ b/UITabBrowser/SearchBar/SearchBarViewModel.swift @@ -16,6 +16,19 @@ class SearchBarViewModel: NSObject { @Published var keywords: String = "" @Published var currentBrowser: Browser? = nil @Published var isSearch = false + @Published var privateMode = false + @Published var iconType: IconType = .Magnifyingglass + + enum IconType: String { + case Magnifyingglass = "magnifyingglass" + case LockShieldFill = "lock.shield" + case LockFill = "lock.fill" + case LockSlash = "lock.slash" + case ShieldFill = "shield.slash" + var name: String { + return self.rawValue + } + } override init() { super.init() @@ -25,14 +38,43 @@ class SearchBarViewModel: NSObject { browsers.$currentBrowser .assign(to: &$currentBrowser) - browsers.$currentBrowser + $currentBrowser .map { $0?.url } + .removeDuplicates() .assign(to: &$url) - browsers.$currentBrowser + $currentBrowser .map { $0?.type == .some(.search) } + .removeDuplicates() .assign(to: &$isSearch) + $currentBrowser + .map { $0?.privateMode ?? false } + .removeDuplicates() + .assign(to: &$privateMode) + + $url + .combineLatest($privateMode) + .combineLatest($isSearch) + .map { + let (url, privateMode) = $0 + let isSearch = $1 + if isSearch { + return .Magnifyingglass + } else { + if let url = url { + if url.absoluteString.isSecureURL { + return privateMode ? .LockShieldFill : .LockFill + } else { + return privateMode ? .ShieldFill : .LockSlash + } + } else { + return .Magnifyingglass + } + } + } + .assign(to: &$iconType) + NotificationCenter.default .publisher(for: SetTextInSearchBarNotification) .compactMap { $0.object as? String } @@ -44,7 +86,7 @@ class SearchBarViewModel: NSObject { openURL(url: url) if !text.isURL { // Save keywords - if let currentBrowser = currentBrowser { + if let currentBrowser = currentBrowser, !currentBrowser.privateMode { items.add( type: .keywords, title: text, @@ -52,8 +94,6 @@ class SearchBarViewModel: NSObject { keywords: text, browserId: currentBrowser.id ) - } else { - print("Exception") } } } @@ -65,18 +105,19 @@ class SearchBarViewModel: NSObject { let sharedBrowsers = Browsers.shared if currentBrowser.type == .search { // if same url tab exists... - if let browser = Browsers.shared.browserOf(url: url) { + if let browser = Browsers.shared.browserOf(url: url), !currentBrowser.privateMode { // move to the tab sharedBrowsers.select(id: browser.id) + // close search view + if let searchViewID = sharedBrowsers.getSearchViewID() { + sharedBrowsers.delete(id: searchViewID) + } } else { - // open a new tab - Browsers.shared.appendBrowser(urlString: url.absoluteString) - } - // Close search view - if let searchViewID = sharedBrowsers.getSearchViewID() { - sharedBrowsers.delete(id: searchViewID) + // open + currentBrowser.openURL(url: url) } } else { + // open currentBrowser.openURL(url: url) } } diff --git a/UITabBrowser/Tabs/TabCell.swift b/UITabBrowser/Tabs/TabCell.swift index 90a0208..777ba2d 100644 --- a/UITabBrowser/Tabs/TabCell.swift +++ b/UITabBrowser/Tabs/TabCell.swift @@ -10,7 +10,18 @@ import UIKit class TabCell: UICollectionViewCell { static let reuseIdentifier = "tab-cell-reuse-identifier" + enum IconType: String { + case none = "" + case privateMode = "shield.lefthalf.fill" + case pinned = "pin.fill" + + var name: String { + self.rawValue + } + } + var imageView = UIImageView() + var secondaryImageView = UIImageView() var titleLabel = UILabel() var activityView = UIActivityIndicatorView(style: .medium) @@ -27,6 +38,26 @@ class TabCell: UICollectionViewCell { } } + private var secondaryImageViewFullWidthConstraint: NSLayoutConstraint! = nil + private var secondaryImageViewZeroWidthConstraint: NSLayoutConstraint! = nil + + private var _iconType: IconType = .none + var iconType: IconType { + get { _iconType } + set { + if newValue == .none { + secondaryImageView.image = UIImage() + NSLayoutConstraint.deactivate([secondaryImageViewFullWidthConstraint]) + NSLayoutConstraint.activate([secondaryImageViewZeroWidthConstraint]) + } else { + secondaryImageView.image = UIImage(systemName: newValue.name) + NSLayoutConstraint.deactivate([secondaryImageViewZeroWidthConstraint]) + NSLayoutConstraint.activate([secondaryImageViewFullWidthConstraint]) + } + _iconType = newValue + } + } + override init(frame: CGRect) { super.init(frame: frame) configureCell() @@ -43,10 +74,26 @@ class TabCell: UICollectionViewCell { extension TabCell { func configureCell() { + let imageSize: CGFloat = 20.0 + let secondaryImageSize: CGFloat = 20.0 + let imageLeadingMargin: CGFloat = 4.0 + let titleLeadingMargin: CGFloat = 4.0 + let titleTrailingMargin: CGFloat = -4.0 + let contentOffsetY: CGFloat = 2.0 + + secondaryImageViewFullWidthConstraint = secondaryImageView.widthAnchor.constraint( + equalToConstant: secondaryImageSize + ) + secondaryImageViewZeroWidthConstraint = secondaryImageView.widthAnchor.constraint( + equalToConstant: .zero + ) + imageView.translatesAutoresizingMaskIntoConstraints = false + secondaryImageView.translatesAutoresizingMaskIntoConstraints = false titleLabel.translatesAutoresizingMaskIntoConstraints = false activityView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(imageView) + contentView.addSubview(secondaryImageView) contentView.addSubview(titleLabel) contentView.addSubview(activityView) @@ -59,12 +106,9 @@ extension TabCell { imageView.contentMode = .scaleAspectFit imageView.tintColor = .lightGray + secondaryImageView.contentMode = .scaleAspectFit + secondaryImageView.tintColor = .lightGray - let imageSize: CGFloat = 20.0 - let imageLeadingMargin: CGFloat = 4.0 - let titleLeadingMargin: CGFloat = 4.0 - let titleTrailingMargin: CGFloat = -4.0 - let contentOffsetY: CGFloat = 2.0 NSLayoutConstraint.activate([ // Progress View activityView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), @@ -88,14 +132,25 @@ extension TabCell { constant: titleLeadingMargin ), titleLabel.trailingAnchor.constraint( - equalTo: contentView.trailingAnchor, - constant: titleTrailingMargin + equalTo: secondaryImageView.leadingAnchor ), titleLabel.heightAnchor.constraint(equalToConstant: imageSize), titleLabel.centerYAnchor.constraint( equalTo: contentView.centerYAnchor, constant: contentOffsetY ), + // Secondary Image View + secondaryImageView.centerYAnchor.constraint( + equalTo: contentView.centerYAnchor, + constant: contentOffsetY + ), + secondaryImageView.trailingAnchor.constraint( + equalTo: contentView.trailingAnchor, + constant: titleTrailingMargin + ), + secondaryImageView.heightAnchor.constraint(equalToConstant: imageSize), + iconType == .none + ? secondaryImageViewZeroWidthConstraint : secondaryImageViewFullWidthConstraint, ]) } } diff --git a/UITabBrowser/Tabs/TabsViewController.swift b/UITabBrowser/Tabs/TabsViewController.swift index 03af565..2aa49ed 100644 --- a/UITabBrowser/Tabs/TabsViewController.swift +++ b/UITabBrowser/Tabs/TabsViewController.swift @@ -140,10 +140,17 @@ extension TabsViewController { tab.title.isEmpty ? NSLocalizedString("Loading...", comment: "in Tab") : tab.title - cell.imageView.image = tab.pinned ? UIImage(systemName: "pin.fill") : tab.favicon + cell.imageView.image = tab.favicon cell.imageView.isHidden = tab.loading cell.isActivityAnimating = tab.loading cell.isSelected = tab.active + if tab.privateMode { + cell.iconType = .privateMode + } else if tab.pinned { + cell.iconType = .pinned + } else { + cell.iconType = .none + } // Background var background = UIBackgroundConfiguration.listPlainCell() @@ -262,7 +269,7 @@ extension TabsViewController { attributes: [] ) { _ in if let url = tab.url { - self.viewModel.openNewTab(url: url) + self.viewModel.openNewTab(url: url, privateMode: tab.privateMode) } } // Open in default browser @@ -282,7 +289,7 @@ extension TabsViewController { let pinAction = UIAction( title: NSLocalizedString("Pin", comment: "in Tab's context menu"), image: UIImage(systemName: "pin"), - attributes: [] + attributes: tab.privateMode ? [.disabled] : [] ) { _ in self.viewModel.setPin(id: tab.id, pinned: true) } @@ -293,6 +300,16 @@ extension TabsViewController { ) { _ in self.viewModel.setPin(id: tab.id, pinned: false) } + // Private mode + /* + let privateAction = UIAction( + title: NSLocalizedString("Private Browse", comment: "in Tab's context menu"), + image: UIImage(systemName: "lock.shield"), + attributes: tab.privateMode || tab.pinned ? [.disabled] : [] + ) { _ in + self.viewModel.setPrivateMode(id: tab.id) + } + */ // Define menu items let children = tab.type == .browser @@ -302,6 +319,7 @@ extension TabsViewController { openDefaultBrowserAction, openNewTabAction, tab.pinned ? unpinAction : pinAction, + // privateAction, reloadAction, bookmarkAction, ] diff --git a/UITabBrowser/Tabs/TabsViewModel.swift b/UITabBrowser/Tabs/TabsViewModel.swift index db00253..52a3555 100644 --- a/UITabBrowser/Tabs/TabsViewModel.swift +++ b/UITabBrowser/Tabs/TabsViewModel.swift @@ -40,7 +40,8 @@ class TabsViewModel: ObservableObject { favicon: browser.favicon, active: browser.selected, loading: browser.loading, - pinned: browser.pinned + pinned: browser.pinned, + privateMode: browser.privateMode ) case .search: return Tab( @@ -51,7 +52,8 @@ class TabsViewModel: ObservableObject { favicon: UIImage(systemName: "magnifyingglass"), active: browser.selected, loading: false, - pinned: false + pinned: false, + privateMode: browser.privateMode ) } } @@ -85,8 +87,8 @@ extension TabsViewModel { ) } - func openNewTab(url: URL) { - browsers.insertBrowser(urlString: url.absoluteString) + func openNewTab(url: URL, privateMode: Bool = false) { + browsers.insertBrowser(urlString: url.absoluteString, privateMode: privateMode) } func setPin(id: BrowserID, pinned: Bool) { @@ -94,4 +96,8 @@ extension TabsViewModel { browsers.setPin(id: browser.id, pinned: pinned) } } + + func setPrivateMode(id: BrowserID, mode: Bool = true) { + browsers.setPrivateMode(id: id, mode: mode) + } } diff --git a/UITabBrowser/ja.lproj/Localizable.strings b/UITabBrowser/ja.lproj/Localizable.strings index 3781570..05acb87 100644 Binary files a/UITabBrowser/ja.lproj/Localizable.strings and b/UITabBrowser/ja.lproj/Localizable.strings differ