Skip to content

Commit e4c14b7

Browse files
committed
Change the search behaviour
1 parent cd213b3 commit e4c14b7

File tree

18 files changed

+318
-185
lines changed

18 files changed

+318
-185
lines changed

ios/MullvadMockData/MullvadREST/ServerRelaysResponse+Stubs.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ public enum ServerRelaysResponseStubs {
9999
latitude: 40.6963302,
100100
longitude: -74.6034843
101101
),
102+
"hr-zag": REST.ServerLocation(
103+
country: "Croatia",
104+
city: "Zagreb",
105+
latitude: 45.821,
106+
longitude: 15.973
107+
),
108+
"bg-sof": REST.ServerLocation(
109+
country: "Bulgaria",
110+
city: "Sofia",
111+
latitude: 42.683333,
112+
longitude: 23.316667
113+
),
114+
"gr-ath": REST.ServerLocation(
115+
country: "Greece",
116+
city: "Athens",
117+
latitude: 37.98381,
118+
longitude: 23.727539
119+
),
102120
],
103121
wireguard: REST.ServerWireguardTunnels(
104122
ipv4Gateway: .loopback,
@@ -289,6 +307,66 @@ public enum ServerRelaysResponseStubs {
289307
shadowsocksExtraAddrIn: nil,
290308
features: .init(daita: .init(), quic: nil, lwo: nil)
291309
),
310+
REST.ServerRelay(
311+
hostname: "hr-zag-wg-001",
312+
active: true,
313+
owned: false,
314+
location: "hr-zag",
315+
provider: "DataPacket",
316+
weight: 100,
317+
ipv4AddrIn: .loopback,
318+
ipv6AddrIn: .loopback,
319+
publicKey: PrivateKey().publicKey.rawValue,
320+
includeInCountry: true,
321+
daita: true,
322+
shadowsocksExtraAddrIn: nil,
323+
features: .init(daita: .init(), quic: nil, lwo: nil)
324+
),
325+
REST.ServerRelay(
326+
hostname: "bg-sof-wg-001",
327+
active: true,
328+
owned: false,
329+
location: "bg-sof",
330+
provider: "M247",
331+
weight: 100,
332+
ipv4AddrIn: .loopback,
333+
ipv6AddrIn: .loopback,
334+
publicKey: PrivateKey().publicKey.rawValue,
335+
includeInCountry: true,
336+
daita: true,
337+
shadowsocksExtraAddrIn: nil,
338+
features: .init(daita: .init(), quic: nil, lwo: nil)
339+
),
340+
REST.ServerRelay(
341+
hostname: "gr-ath-wg-101",
342+
active: true,
343+
owned: false,
344+
location: "gr-ath",
345+
provider: "DataPacket",
346+
weight: 100,
347+
ipv4AddrIn: .loopback,
348+
ipv6AddrIn: .loopback,
349+
publicKey: PrivateKey().publicKey.rawValue,
350+
includeInCountry: true,
351+
daita: true,
352+
shadowsocksExtraAddrIn: nil,
353+
features: .init(daita: .init(), quic: nil, lwo: nil)
354+
),
355+
REST.ServerRelay(
356+
hostname: "us-nyc-wg-101",
357+
active: true,
358+
owned: false,
359+
location: "us-nyc",
360+
provider: "DataPacket",
361+
weight: 100,
362+
ipv4AddrIn: .loopback,
363+
ipv6AddrIn: .loopback,
364+
publicKey: PrivateKey().publicKey.rawValue,
365+
includeInCountry: true,
366+
daita: true,
367+
shadowsocksExtraAddrIn: nil,
368+
features: .init(daita: .init(), quic: nil, lwo: nil)
369+
),
292370
],
293371
shadowsocksPortRanges: shadowsocksPortRanges
294372
),

ios/MullvadVPN.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@
447447
58FF9FEA2B07653800E4C97D /* ButtonCellContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE92B07653800E4C97D /* ButtonCellContentView.swift */; };
448448
58FF9FF02B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */; };
449449
58FF9FF42B07C61B00E4C97D /* AccessMethodValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */; };
450-
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; };
450+
7A09C98129D99215000C2CAC /* String+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+Search.swift */; };
451451
7A0B311E2B303A0D004B12E0 /* AccessbilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */; };
452452
7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */; };
453453
7A0EAE9A2D01B41500D3EB8B /* MainButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */; };
@@ -815,7 +815,7 @@
815815
A9A5F9ED2ACB05160083449F /* NSRegularExpression+IPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */; };
816816
A9A5F9EE2ACB05160083449F /* StorePaymentOutcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67828F83CA50033DD93 /* StorePaymentOutcome.swift */; };
817817
A9A5F9EF2ACB05160083449F /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; };
818-
A9A5F9F02ACB05160083449F /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; };
818+
A9A5F9F02ACB05160083449F /* String+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+Search.swift */; };
819819
A9A5F9F12ACB05160083449F /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Helpers.swift */; };
820820
A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */; };
821821
A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B75402668FD7700DEF7E9 /* AccountExpirySystemNotificationProvider.swift */; };
@@ -2064,7 +2064,7 @@
20642064
58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistentAccessMethod+ViewModel.swift"; sourceTree = "<group>"; };
20652065
58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodValidationError.swift; sourceTree = "<group>"; };
20662066
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = "<group>"; };
2067-
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; };
2067+
7A09C98029D99215000C2CAC /* String+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Search.swift"; sourceTree = "<group>"; };
20682068
7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessbilityIdentifier.swift; sourceTree = "<group>"; };
20692069
7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Coordinator+Router.swift"; sourceTree = "<group>"; };
20702070
7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainButtonStyle.swift; sourceTree = "<group>"; };
@@ -3501,7 +3501,7 @@
35013501
58B9EB142489139B00095626 /* RESTError+Display.swift */,
35023502
E158B35F285381C60002F069 /* String+AccountFormatting.swift */,
35033503
7AA564472F2B680D001D1FB9 /* String+Assets.swift */,
3504-
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */,
3504+
7A09C98029D99215000C2CAC /* String+Search.swift */,
35053505
5807E2BF2432038B00F5FF30 /* String+Helpers.swift */,
35063506
58CEB2F82AFD136E00E6E088 /* UIBackgroundConfiguration+Extensions.swift */,
35073507
5891BF5025E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift */,
@@ -6105,7 +6105,7 @@
61056105
7A9BE5A72B907EEC00E2A7D0 /* AllLocationDataSource.swift in Sources */,
61066106
A9A5F9EF2ACB05160083449F /* String+AccountFormatting.swift in Sources */,
61076107
F9EDB26C2EC4C0480015DE36 /* CustomListInteractorTests.swift in Sources */,
6108-
A9A5F9F02ACB05160083449F /* String+FuzzyMatch.swift in Sources */,
6108+
A9A5F9F02ACB05160083449F /* String+Search.swift in Sources */,
61096109
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */,
61106110
7A460FA02F35D05A005A265D /* NewAppVersionInAppNotificationProvider.swift in Sources */,
61116111
F0FA16152D7F3E16007E2546 /* Collection+Sorting.swift in Sources */,
@@ -6827,7 +6827,7 @@
68276827
7A5869B72B56B41500640D27 /* IPOverrideTextViewController.swift in Sources */,
68286828
58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */,
68296829
F96D04E92EC317B9004A4D48 /* ExitLocationView.swift in Sources */,
6830-
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */,
6830+
7A09C98129D99215000C2CAC /* String+Search.swift in Sources */,
68316831
F02F41A12B9723AF00625A4F /* AddLocationsDataSource.swift in Sources */,
68326832
58EFC76E2AFB3BDA00E9F4CB /* ListAccessMethodCoordinator.swift in Sources */,
68336833
5827B0B92B14A1C700CCBBA1 /* MethodTestingStatusCellContentConfiguration.swift in Sources */,

ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 0 additions & 15 deletions
This file was deleted.

ios/MullvadVPN/Extensions/String+FuzzyMatch.swift

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//
2+
// String+Search.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2023-04-02.
6+
// Copyright © 2026 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
enum SearchScore: Comparable {
12+
case none
13+
case score(Int)
14+
15+
static func < (lhs: SearchScore, rhs: SearchScore) -> Bool {
16+
switch (lhs, rhs) {
17+
case (.none, .none): return false
18+
case (.none, _): return true
19+
case (_, .none): return false
20+
case (.score(let l), .score(let r)): return l < r
21+
}
22+
}
23+
}
24+
25+
extension String {
26+
func search(_ query: String) -> SearchScore {
27+
guard !query.isEmpty else { return .none }
28+
29+
let text = self.lowercased()
30+
let query = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
31+
32+
// Substring match
33+
if let range = text.range(of: query) {
34+
let index = text.distance(from: text.startIndex, to: range.lowerBound)
35+
36+
// Starts with
37+
if index == 0 {
38+
return .score(1000)
39+
}
40+
41+
var currentIndex = 0
42+
let words = text.split(separator: " ")
43+
for word in words {
44+
if word.hasPrefix(query) {
45+
// earlier word = higher score
46+
return .score(800 - currentIndex)
47+
}
48+
currentIndex += word.count + 1
49+
}
50+
51+
// Contains match
52+
return .score(500 - index)
53+
} else if fuzzyMatch(text, query: query) {
54+
return .score(300)
55+
}
56+
return .none
57+
}
58+
59+
private func fuzzyMatch(_ text: String, query: String) -> Bool {
60+
var tIndex = text.startIndex
61+
62+
for char in query {
63+
if let found = text[tIndex...].firstIndex(of: char) {
64+
tIndex = text.index(after: found)
65+
} else {
66+
return false
67+
}
68+
}
69+
70+
return true
71+
}
72+
}

0 commit comments

Comments
 (0)