Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c9327a9
Use generated letters on autofill Icons
afterxleep Aug 9, 2023
30d1ee0
Added random colors
afterxleep Aug 10, 2023
29fbc77
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 10, 2023
65c29b1
Autofill login icons
afterxleep Aug 11, 2023
bb812fb
Autofill icons
afterxleep Aug 11, 2023
f4b456e
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 11, 2023
88828b8
Point to BSK branch
afterxleep Aug 11, 2023
4e11d54
BSK branch
afterxleep Aug 11, 2023
88868c3
Removed Random colors
afterxleep Aug 11, 2023
c29253d
Cleanup
afterxleep Aug 11, 2023
bb1047b
Moved dependencies to viewController
afterxleep Aug 16, 2023
eaab79c
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 16, 2023
d2578fc
Fix conflicts
afterxleep Aug 16, 2023
75674cd
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 16, 2023
74ca8a7
Use the new forString Method
afterxleep Aug 16, 2023
e22a5d1
Lint fixes
afterxleep Aug 16, 2023
5eed980
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 16, 2023
484716b
Point to BSK Release
afterxleep Aug 16, 2023
a3b317e
Letter Icon Views to BSK
afterxleep Aug 17, 2023
ca2db6a
Lint cleanup
afterxleep Aug 17, 2023
72ca103
Fixed logic
afterxleep Aug 17, 2023
310d101
Lint fixes
afterxleep Aug 17, 2023
0aab3ff
Moved to 2-letter icons
afterxleep Aug 18, 2023
ef33b75
Updated Font-size
afterxleep Aug 18, 2023
e856175
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 18, 2023
af661d4
Point to BSK Branch
afterxleep Aug 18, 2023
bf72b0a
Letter icon View
afterxleep Aug 21, 2023
6246669
Removed unnecesary file
afterxleep Aug 21, 2023
2f21666
Netp local package
afterxleep Aug 21, 2023
92e0623
Missing public properties and imports
afterxleep Aug 21, 2023
cc928e1
Public props and Explicit initializer
afterxleep Aug 21, 2023
789aa03
Merge branch 'develop' into daniel/autofill-icon-letters
afterxleep Aug 21, 2023
4ecb0ce
Fixing lint
afterxleep Aug 21, 2023
481481a
Dynamic Padding updates
afterxleep Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10234,8 +10234,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 75.0.2;
kind = exactVersion;
version = 75.0.2;
};
};
AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Common/View/SwiftUI/FaviconView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ struct FaviconView: View {
ZStack {
let eTLDplus1 = ContentBlocking.shared.tld.eTLDplus1(domain) ?? domain
Rectangle()
.foregroundColor(Color.forDomain(eTLDplus1))
.foregroundColor(Color.forString(eTLDplus1))
Text(String(eTLDplus1.capitalized.first ?? "?"))
.font(.title)
.foregroundColor(Color.white)
Expand Down
29 changes: 15 additions & 14 deletions DuckDuckGo/Common/View/SwiftUI/LoginFaviconView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,30 @@
//

import SwiftUI
import BrowserServicesKit
import SwiftUIExtensions

struct LoginFaviconView: View {

let domain: String?

let domain: String
let generatedIconLetters: String
let faviconManagement: FaviconManagement = FaviconManager.shared

var body: some View {

if let image = favicon {
Image(nsImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 32)
.cornerRadius(4.0)
Group {
if let image = faviconManagement.getCachedFavicon(for: domain, sizeCategory: .small)?.image {
Image(nsImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 32)
.cornerRadius(4.0)
.padding(.leading, 6)
} else {
LetterIconView(title: generatedIconLetters)
}
}

}

var favicon: NSImage? {
guard let domain else {
return NSImage(named: "Login")
}
return faviconManagement.getCachedFavicon(for: domain, sizeCategory: .small)?.image ?? NSImage(named: "Login")
}

Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/PinnedTabs/View/PinnedTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ struct PinnedTabInnerView: View {
} else if let domain = model.content.url?.host, let eTLDplus1 = ContentBlocking.shared.tld.eTLDplus1(domain), let firstLetter = eTLDplus1.capitalized.first.flatMap(String.init) {
ZStack {
Rectangle()
.foregroundColor(.forDomain(eTLDplus1))
.foregroundColor(.forString(eTLDplus1))
Text(firstLetter)
.font(.caption)
.foregroundColor(.white)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,18 @@ final class PasswordManagementItemListModel: ObservableObject {
@Published var canChangeCategory: Bool = true

private var onItemSelected: (_ old: SecureVaultItem?, _ new: SecureVaultItem?) -> Void
private let tld: TLD
private let urlMatcher: AutofillDomainNameUrlMatcher
private static let randomColorsCount = 15

init(passwordManagerCoordinator: PasswordManagerCoordinating,
onItemSelected: @escaping (_ old: SecureVaultItem?, _ new: SecureVaultItem?) -> Void) {
onItemSelected: @escaping (_ old: SecureVaultItem?, _ new: SecureVaultItem?) -> Void,
urlMatcher: AutofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher(),
tld: TLD = ContentBlocking.shared.tld) {
self.onItemSelected = onItemSelected
self.passwordManagerCoordinator = passwordManagerCoordinator
self.urlMatcher = urlMatcher
self.tld = tld
}

func update(items: [SecureVaultItem]) {
Expand Down Expand Up @@ -451,4 +458,10 @@ final class PasswordManagementItemListModel: ObservableObject {
}
}

func tldForAccount(_ account: SecureVaultModels.WebsiteAccount) -> String {
let name = account.name(tld: tld, autofillDomainNameUrlMatcher: urlMatcher)
let title = (account.title?.isEmpty == false) ? account.title! : "#"
return tld.eTLDplus1(name) ?? title
}

}
19 changes: 15 additions & 4 deletions DuckDuckGo/SecureVault/Model/PasswordManagementLoginModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import Combine
import BrowserServicesKit
import Common

final class PasswordManagementLoginModel: ObservableObject, PasswordManagementItemModel {

Expand All @@ -32,7 +33,7 @@ final class PasswordManagementLoginModel: ObservableObject, PasswordManagementIt

var onSaveRequested: (SecureVaultModels.WebsiteCredentials) -> Void
var onDeleteRequested: (SecureVaultModels.WebsiteCredentials) -> Void
var urlMatcher: AutofillUrlMatcher
var urlMatcher: AutofillDomainNameUrlMatcher
var emailManager: EmailManager

var isEditingPublisher: Published<Bool>.Publisher {
Expand All @@ -52,6 +53,7 @@ final class PasswordManagementLoginModel: ObservableObject, PasswordManagementIt
@Published var notes: String = ""
@Published var isEditing = false
@Published var isNew = false
@Published var domainTLD = ""

var isDirty: Bool {
title != "" || username != "" || password != "" || domain != "" || notes != ""
Expand Down Expand Up @@ -121,16 +123,23 @@ final class PasswordManagementLoginModel: ObservableObject, PasswordManagementIt
usernameIsPrivateEmail && privateEmailMessage != ""
}

private let tld: TLD
private let urlSort: AutofillDomainNameUrlSort
private static let randomColorsCount = 15

init(onSaveRequested: @escaping (SecureVaultModels.WebsiteCredentials) -> Void,
onDeleteRequested: @escaping (SecureVaultModels.WebsiteCredentials) -> Void,
urlMatcher: AutofillUrlMatcher = AutofillDomainNameUrlMatcher(),
emailManager: EmailManager = EmailManager()) {
urlMatcher: AutofillDomainNameUrlMatcher,
emailManager: EmailManager,
tld: TLD = ContentBlocking.shared.tld,
urlSort: AutofillDomainNameUrlSort) {
self.onSaveRequested = onSaveRequested
self.onDeleteRequested = onDeleteRequested
self.urlMatcher = urlMatcher
self.emailManager = emailManager
self.tld = tld
self.urlSort = urlSort
self.emailManager.requestDelegate = self

}

func setSecureVaultModel<Model>(_ modelObject: Model) {
Expand Down Expand Up @@ -201,6 +210,8 @@ final class PasswordManagementLoginModel: ObservableObject, PasswordManagementIt
domain = urlMatcher.normalizeUrlForWeb(credentials?.account.domain ?? "")
notes = credentials?.account.notes ?? ""
isNew = credentials?.account.id == nil
let name = credentials?.account.name(tld: tld, autofillDomainNameUrlMatcher: urlMatcher)
domainTLD = tld.eTLDplus1(name) ?? credentials?.account.title ?? "#"

// Determine Private Email Status when required
usernameIsPrivateEmail = emailManager.isPrivateEmail(email: username)
Expand Down
17 changes: 14 additions & 3 deletions DuckDuckGo/SecureVault/View/PasswordManagementItemList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Foundation
import SwiftUI
import BrowserServicesKit
import Combine
import SwiftUIExtensions

struct ScrollOffsetKey: PreferenceKey {
typealias Value = CGFloat
Expand Down Expand Up @@ -267,6 +268,13 @@ private struct ItemView: View {
let item: SecureVaultItem
let action: () -> Void

func getIconLetters(account: SecureVaultModels.WebsiteAccount) -> String {
if let title = account.title, !title.isEmpty {
return title
}
return model.tldForAccount(account)
}

var body: some View {

let selected = model.selected == item
Expand All @@ -277,9 +285,12 @@ private struct ItemView: View {
HStack(spacing: 2) {

switch item {
case .account(let account):
LoginFaviconView(domain: account.domain)
.padding(.leading, 6)
case .account:
if let account = item.websiteAccount, let domain = account.domain {
LoginFaviconView(domain: domain, generatedIconLetters: getIconLetters(account: account))
} else {
LetterIconView(title: "#")
}
case .card:
Image("Card")
.frame(width: 32)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,12 +588,18 @@ private struct HeaderView: View {

@EnvironmentObject var model: PasswordManagementLoginModel

private func getIconLetters() -> String {
return !model.title.isEmpty ? model.title :
!model.domainTLD.isEmpty ? model.domainTLD :
"#"
}

var body: some View {

HStack(alignment: .center, spacing: 0) {

LoginFaviconView(domain: model.domain)
.padding(.trailing, 10)
LoginFaviconView(domain: model.domain,
generatedIconLetters: getIconLetters())
.padding(.trailing, 10)

if model.isNew || model.isEditing {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ final class PasswordManagementViewController: NSViewController {

private let passwordManagerCoordinator: PasswordManagerCoordinating = PasswordManagerCoordinator.shared

private let emailManager = EmailManager()
private let urlMatcher = AutofillDomainNameUrlMatcher()
private let tld = ContentBlocking.shared.tld
private let urlSort = AutofillDomainNameUrlSort()

override func viewDidLoad() {
super.viewDidLoad()
createListView()
Expand Down Expand Up @@ -395,7 +400,10 @@ final class PasswordManagementViewController: NSViewController {
self?.doSaveCredentials(credentials)
}, onDeleteRequested: { [weak self] credentials in
self?.promptToDelete(credentials: credentials)
})
},
urlMatcher: urlMatcher,
emailManager: emailManager,
urlSort: urlSort)

self.itemModel = itemModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public extension Color {
)
}

static func forDomain(_ domain: String) -> Color {
static func forString(_ string: String) -> Color {
var consistentHash: Int {
return domain.utf8
return string.utf8
.map { return $0 }
.reduce(5381) { ($0 << 5) &+ $0 &+ Int($1) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// FaviconLetterView.swift
//
// Copyright © 2023 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

public struct LetterIconView: View {

public var title: String
public var size: CGFloat
public var prefferedFirstCharacters: String?
public var characterCount: Int
private var padding: CGFloat = 0.33

private var characters: String {
if let prefferedFirstCharacters = prefferedFirstCharacters,
prefferedFirstCharacters != "" {
return String(prefferedFirstCharacters.prefix(characterCount))
}
return String(title.prefix(characterCount))
}

public init(title: String, size: CGFloat = 32, prefferedFirstCharacters: String? = nil, characterCount: Int = 2) {
self.title = title
self.size = size
self.prefferedFirstCharacters = prefferedFirstCharacters
self.characterCount = characterCount
}

public var body: some View {
ZStack {
RoundedRectangle(cornerRadius: size * 0.125)
.foregroundColor(Color.forString(title))
.frame(width: size, height: size)

Text(characters.capitalized(with: .current))
.frame(width: size - (size * padding), height: size - (size * padding))
.foregroundColor(.white)
.minimumScaleFactor(0.01)
.font(.system(size: size, weight: .semibold))
}
.padding(.leading, 8)
}
}
24 changes: 20 additions & 4 deletions UnitTests/SecureVault/PasswordManagementItemModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ final class PasswordManagementItemModelTests: XCTestCase {
var isDirty = false
var savedCredentials: SecureVaultModels.WebsiteCredentials?
var deletedCredentials: SecureVaultModels.WebsiteCredentials?
var urlMatcher = AutofillDomainNameUrlMatcher()
var emailManager = EmailManager()
var tld = ContentBlocking.shared.tld
var urlSort = AutofillDomainNameUrlSort()

func testWhenCredentialsAreSavedThenSaveIsRequested() {
let model = PasswordManagementLoginModel(onSaveRequested: onSaveRequested,
onDeleteRequested: onDeleteRequested)
onDeleteRequested: onDeleteRequested,
urlMatcher: urlMatcher,
emailManager: emailManager,
urlSort: urlSort)

model.credentials = makeCredentials(id: "1")
model.save()
Expand All @@ -39,7 +46,10 @@ final class PasswordManagementItemModelTests: XCTestCase {

func testWhenCredentialsAreDeletedThenDeleteIsRequested() {
let model = PasswordManagementLoginModel(onSaveRequested: onSaveRequested,
onDeleteRequested: onDeleteRequested)
onDeleteRequested: onDeleteRequested,
urlMatcher: urlMatcher,
emailManager: emailManager,
urlSort: urlSort)

model.credentials = makeCredentials(id: "1")
model.requestDelete()
Expand All @@ -50,7 +60,10 @@ final class PasswordManagementItemModelTests: XCTestCase {

func testWhenCredentialsHasNoIdThenModelStateIsNew() {
let model = PasswordManagementLoginModel(onSaveRequested: onSaveRequested,
onDeleteRequested: onDeleteRequested)
onDeleteRequested: onDeleteRequested,
urlMatcher: urlMatcher,
emailManager: emailManager,
urlSort: urlSort)

model.createNew()

Expand All @@ -62,7 +75,10 @@ final class PasswordManagementItemModelTests: XCTestCase {

func testWhenModelIsEditedThenStateIsUpdated() {
let model = PasswordManagementLoginModel(onSaveRequested: onSaveRequested,
onDeleteRequested: onDeleteRequested)
onDeleteRequested: onDeleteRequested,
urlMatcher: urlMatcher,
emailManager: emailManager,
urlSort: urlSort)

model.credentials = makeCredentials(id: "1")
XCTAssertEqual(model.domain, "domain")
Expand Down