Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TabsListManager refactor to have use cases #72

Merged
merged 26 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5eeec74
Start work on tabs use cases to not have one big tabs list manager wh…
kyzmitch Jan 4, 2024
086b314
Finish ReadTabUseCase and UseCaseFactory, add service commands to Tab…
kyzmitch Jan 20, 2024
d172b57
Integrate more use cases and create new view models to leverage them
kyzmitch Jan 21, 2024
54cb79b
Fix compilation
kyzmitch Jan 23, 2024
d31acab
Fix compilation of CoreBrowser tests
kyzmitch Jan 23, 2024
226c23f
Fix use case locator crash
kyzmitch Jan 25, 2024
de9aad9
Combine last 4 commits
kyzmitch Jan 28, 2024
2417964
Solve complex use case type registration issue
kyzmitch Jan 28, 2024
5e05ca3
Rename use case classes
kyzmitch Jan 28, 2024
92538ee
Doc comments, attempt to fix SwiftUI site loading (web view model cre…
kyzmitch Jan 28, 2024
9466041
Attempt to fix web view model creation
kyzmitch Jan 28, 2024
f2cc716
Create web view model without a specific site to solve the async init…
kyzmitch Jan 28, 2024
2a276f1
Use different approach to be able to store VM as a state object
kyzmitch Jan 31, 2024
d2d4000
Use environment object to make the constructors smaller, also use nor…
kyzmitch Feb 1, 2024
7091f08
Merge branch 'master' into feature/ios_tabs_use_cases
kyzmitch Feb 3, 2024
6ca4683
Update copyright header for the new files from this PR
kyzmitch Feb 3, 2024
78fab7c
Fix content type reloading to the default one from the async task in …
kyzmitch Feb 3, 2024
b1f9b06
Add mocks auto-generation for the use cases, currently primary associ…
kyzmitch Feb 4, 2024
d62d8e3
Fix search suggestions vm tests compilation after adding use case
kyzmitch Feb 4, 2024
d010483
Fix CottonData unit tests compilation
kyzmitch Feb 4, 2024
7b87eaf
Fix search suggestions view model tests
kyzmitch Feb 4, 2024
22260d7
Fix web view model tests
kyzmitch Feb 4, 2024
6fc99b3
Fix client compilation
kyzmitch Feb 4, 2024
f17ea9c
fix linter
kyzmitch Feb 4, 2024
14858f4
Swiftlint format result
kyzmitch Feb 4, 2024
0d9dedc
Fix lint errors for sure
kyzmitch Feb 4, 2024
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
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ clean:
ios-lint:
swiftlint --version; \
swiftlint lint catowseriOS --config catowseriOS/.swiftlint.yml --quiet; \

.PHONY: ios-lint-format
ios-lint-format:
swiftlint --version; \
swiftlint lint catowseriOS --config catowseriOS/.swiftlint.yml --quiet --autocorrect --format; \

.PHONY: android-lint
android-lint:
Expand Down
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

# Cotton - web browser for iOS & Android

Features
-----------------
## Features
- web search autocomplete (DuckDuckGo, google)
- DNS over https (google) alpha state (can't fully support without VPN profiles)
- tabs
Expand All @@ -14,10 +13,8 @@ Features
- tabs cache/restore to be able to see the same web sites after app restart
- own JavaScript plugins (instagram content downloads, html video tags downloads) experimental state

Building the code
-----------------
Environment
-----------------
## Building the code
### Environment
- IntelliJ IDEA 2023.2.4 (Community Edition)
- Xcode 15.0.0
- Android Studio Giraffe | 2022.3.1 Patch 4
Expand All @@ -26,8 +23,11 @@ Environment
- Kotlin 1.9.20
- Gradle 7.4.2 for build.gradle.kts in IndelliJ IDEA, but can use 8.1 in wrapper. In Android Studio it is 7.3.0 in build.gradle.kts and 7.4 in wrapper.

Steps
-----------------
### Steps
#### Make commands
If you have everything installed, then use `make help` to see how to build `cotton-base` which is needed for Xcode project, if no, then do the following steps

#### Full clear setup
- install `Android Studio` to have Android SDK for Kotlin Multiplatform project even for iOS build, because gradle file depends on it as well.
- On macOS update your `.bash_profile` with
```
Expand All @@ -48,17 +48,19 @@ export PATH="$PATH:/usr/local/Cellar/gradle@7/7.6.2/bin"
- Tools -> Android -> SDK Manager
- SDK Tools tab
- Install `Google Play Licensing Library`
- Run `cd cotton-base` & `gradle wrapper` if you see an error about Gradle build.
- `make`
- open Kotlin `cotton-base` folder using `InteliJ IDEA`
- run `cotton-base [assembleCottonBaseReleaseXCFramework]` Gradle configuration for iOS client. It is located under `other` section of Gradle tasks list.
- run `cotton-base [publishAndroidDebugPublicationToMavenLocal]` Gradle configuration for Android client
- for iOS client:
- `cd catowseriOS/`
- Open `catowser.xcworkspace`
- Build `Cotton` or `Cotton dev` build target
- for Android client:
- open `catowserAndroid` using Android Studio

#### CottonBase common dependency
##### for iOS client
- run `make build-cotton-base-ios-release`
- `cd catowseriOS/`
- `open catowser.xcworkspace`
- Build `Cotton` or `Cotton dev` build target from Xcode

##### for Android client
1. if you use Terminal run `make build-cotton-base-android-release`
2. if you use IntelliJ IDEA run `cotton-base [publishAndroidDebugPublicationToMavenLocal]` Gradle task

Open `catowserAndroid` folder using Android Studio after that.

Design documents
-----------------
Expand Down
38 changes: 19 additions & 19 deletions brew_configs/Brewfile.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,40 +117,40 @@
}
},
"./brew_configs/xcode-kotlin.rb": {
"version": "1.2.1",
"version": "1.3.0",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_sonoma": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:69339f0fddf76017f1ad97cc0e276f6b74530eb4ff2d7a54516d17da529e621d",
"sha256": "69339f0fddf76017f1ad97cc0e276f6b74530eb4ff2d7a54516d17da529e621d"
},
"arm64_ventura": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:e94099f5806bca9e80329cbada36c0ab0aacdfdc65101db014098ea22d6e7d97",
"sha256": "e94099f5806bca9e80329cbada36c0ab0aacdfdc65101db014098ea22d6e7d97"
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:e0f7980586495f08a0b40a6b8bd945ebb51b118dd8e4766f3b9ea3568a932e33",
"sha256": "e0f7980586495f08a0b40a6b8bd945ebb51b118dd8e4766f3b9ea3568a932e33"
},
"arm64_monterey": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:f285f107b7d74b83cee5b57f309270176e40fb26a7b19a9090d467e5dc35074e",
"sha256": "f285f107b7d74b83cee5b57f309270176e40fb26a7b19a9090d467e5dc35074e"
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:8651ca5f1d64ec6e6cc7c33327e8b386e29a028a073071661a954815d562b776",
"sha256": "8651ca5f1d64ec6e6cc7c33327e8b386e29a028a073071661a954815d562b776"
},
"arm64_big_sur": {
"sonoma": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:70b8e27a5a2a615a3a2ee1bbf0d929f8acdd6e9b95f26e4147bd11ce1f48e2d9",
"sha256": "70b8e27a5a2a615a3a2ee1bbf0d929f8acdd6e9b95f26e4147bd11ce1f48e2d9"
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:ba58e37491c17a535b333023eff8d9b197f4439b06a6feccd3e471a7016fafb9",
"sha256": "ba58e37491c17a535b333023eff8d9b197f4439b06a6feccd3e471a7016fafb9"
},
"ventura": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:dc860cbe80e94025eb00c3148de32aa929236839f30a7f4a1918fd0598fe6234",
"sha256": "dc860cbe80e94025eb00c3148de32aa929236839f30a7f4a1918fd0598fe6234"
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:3683e2bf57b6114acc7a209a28883acea95e2bbc3eb8c6e193a8ad5ac2a85f21",
"sha256": "3683e2bf57b6114acc7a209a28883acea95e2bbc3eb8c6e193a8ad5ac2a85f21"
},
"monterey": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:f23e73486a28a283674648c3bea20e8237fa6c740548dc45a90d1063b5173034",
"sha256": "f23e73486a28a283674648c3bea20e8237fa6c740548dc45a90d1063b5173034"
},
"big_sur": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:f3f93d8e5ee666bd54ed69f40d5557059a1dd1d58b8a2f6ecb4f63870d28004a",
"sha256": "f3f93d8e5ee666bd54ed69f40d5557059a1dd1d58b8a2f6ecb4f63870d28004a"
"url": "https://ghcr.io/v2/homebrew/core/xcode-kotlin/blobs/sha256:6e9592f228937c68cfa06ea3aabf0f4286f72da78cba2e0198f7f20b0e157a78",
"sha256": "6e9592f228937c68cfa06ea3aabf0f4286f72da78cba2e0198f7f20b0e157a78"
}
}
}
Expand All @@ -176,12 +176,12 @@
"macOS": "13.4.1"
},
"sonoma": {
"HOMEBREW_VERSION": "4.1.17",
"HOMEBREW_VERSION": "4.2.5",
"HOMEBREW_PREFIX": "/usr/local",
"Homebrew/homebrew-core": "api",
"CLT": "",
"Xcode": "15.0",
"macOS": "14.0"
"macOS": "14.2.1"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import CottonBase
public final class AlamofireReachabilityAdaptee<S: ServerDescription>: NetworkReachabilityAdapter {
let connectivityManager: NetworkReachabilityManager
public typealias Server = S

public init?(server: Server) {
if let manager = NetworkReachabilityManager(host: server.host.rawString) {
connectivityManager = manager
Expand All @@ -24,14 +24,14 @@ public final class AlamofireReachabilityAdaptee<S: ServerDescription>: NetworkRe
return nil
}
}

public func startListening(onQueue queue: DispatchQueue, onUpdatePerforming listener: @escaping Listener) -> Bool {
let closure = { (status: Alamofire.NetworkReachabilityManager.NetworkReachabilityStatus) -> Void in
listener(status.httpKitValue)
}
return connectivityManager.startListening(onQueue: queue, onUpdatePerforming: closure)
}

public func stopListening() {
connectivityManager.stopListening()
}
Expand Down
14 changes: 7 additions & 7 deletions catowseriOS/BrowserNetworking/Downloadable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension Downloadable {
public var excludeFromBackup: Bool {
return true
}

public var fileName: String {
if #available(iOS 13.0, *) {
var md5Hasher = Insecure.MD5()
Expand All @@ -49,7 +49,7 @@ extension Downloadable {
return "\(hostname)_\(hasher.finalize())"
}
}

public func fileAtDestination() -> URL? {
do {
let destination = try sandboxDestination()
Expand All @@ -62,7 +62,7 @@ extension Downloadable {
return nil
}
}

/// Path to temporary file to not waste RAM.
/// You can't participate in the files app (or iTunes File Sharing)
/// if you don't store your files in the Documents folder.
Expand Down Expand Up @@ -131,10 +131,10 @@ extension BrowserNetworking {
fileprivate var appGroupIdentifier: String {
"group.com.ae.cotton-browser"
}

public typealias FileDownloadProducer = SignalProducer<ProgressResponse<URL>, DownloadError>
public typealias RemoteFileInfoProducer = SignalProducer<Int, DownloadError>

/// Sends download request for remote file
///
/// - Parameter file: All info about remote file and info about how it should be saved
Expand Down Expand Up @@ -179,15 +179,15 @@ extension BrowserNetworking {

return producer
}

/**
Fetches info of remote file like file size which expect to be dowloaded.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length
The Content-Length entity header indicates the size of the entity-body, in bytes, sent to the recipient.
https://tools.ietf.org/html/rfc7230#section-3.3.2
A server MAY send a Content-Length header field in a response to a
HEAD request.

*/
public static func fetchRemoteResourceInfo(url: URL) -> RemoteFileInfoProducer {
let producer = RemoteFileInfoProducer { (observer, _) in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension RestClient where Server == GoogleDnsServer {
guard let hostString = url.httpHost else {
throw HttpError.noHostInUrl
}

let ipAddressResponse = try await self.aaGetIPaddress(ofDomain: hostString)
return try url.updatedHost(with: ipAddressResponse.ipAddress)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public typealias GDNSjsonProducer = SignalProducer<GoogleDNSOverJSONResponse, Ht
public typealias GDNSjsonPublisher = AnyPublisher<GoogleDNSOverJSONResponse, HttpError>

extension Endpoint where S == GoogleDnsServer {

static func googleDnsOverHTTPSJson(_ params: GDNSRequestParams) throws -> GDNSjsonEndpoint {
/**
To minimize this risk, send only the HTTP headers required for DoH:
Expand All @@ -50,13 +50,13 @@ extension Endpoint where S == GoogleDnsServer {
encodingMethod: .QueryString(items: params.urlQueryItems.kotlinArray))
return frozenEndpoint
}

static func googleDnsOverHTTPSJson(_ domainName: String) throws -> GDNSjsonEndpoint {
let domainObject = try DomainName(input: domainName)
guard let params = GDNSRequestParams(domainName: domainObject) else {
throw HttpError.missingRequestParameters("google dns params")
}

return try .googleDnsOverHTTPSJson(params)
}
}
Expand All @@ -71,19 +71,19 @@ public struct GoogleDNSOverJSONResponse: ResponseType {
public static var successCodes: [Int] {
[200]
}

fileprivate let answer: [Answer]
/**
Note: An HTTP success may still be a DNS failure.
Check the DNS response code (JSON "Status" field) for the
DNS errors SERVFAIL, FORMERR, REFUSED, and NOTIMP.
*/
*/
let status: Int32
/// NOERROR - Standard DNS response code (32 bit integer).
let noError: Int32 = 0

public let ipAddress: String

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
answer = try container.decode([Answer].self, forKey: .answer)
Expand All @@ -97,7 +97,7 @@ public struct GoogleDNSOverJSONResponse: ResponseType {
}
ipAddress = firstAddress
}

fileprivate enum CodingKeys: String, CodingKey {
case answer = "Answer"
case status = "Status"
Expand All @@ -108,7 +108,7 @@ public enum GoogleDNSEndpointError: LocalizedError {
case emptyAnswers
case dnsStatusError(Int32)
case recordTypeParsing(UInt32)

public var errorDescription: String? {
return "Google DSN over JSON `\(self)`"
}
Expand All @@ -124,7 +124,7 @@ private struct Answer: Decodable {
let recordType: DnsRR
/// Data for A - IP address as text or some different thing like `z-p42-instagram.c10r.facebook.com.`
let ipAddress: String

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
Expand All @@ -135,7 +135,7 @@ private struct Answer: Decodable {
}
recordType = dnsRR
}

fileprivate enum CodingKeys: String, CodingKey {
case name
case ipAddress = "data"
Expand Down Expand Up @@ -166,14 +166,14 @@ extension RestClient where Server == GoogleDnsServer {
} catch {
return GDNSjsonProducer(error: HttpError.failedConstructRequestParameters)
}

let adapter: AlamofireHTTPRxAdaptee<GoogleDNSOverJSONResponse,
GoogleDnsServer,
GDNSjsonRxInterface> = .init(.waitsForRxObserver)
let producer = self.rxMakePublicRequest(for: endpoint, transport: adapter, subscriber: subscriber)
return producer
}

public func rxResolvedDomainName(in url: URL, _ subscriber: GDNSJsonClientRxSubscriber) -> ResolvedURLProducer {
return url.rxHttpHost
.flatMapError({ _ -> SignalProducer<String, HttpError> in
Expand All @@ -189,7 +189,7 @@ extension RestClient where Server == GoogleDnsServer {
return url.rxUpdatedHost(with: response.ipAddress)
})
}

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
func cGetIPaddress(ofDomain domainName: String, _ subscriber: GDNSJsonClientSubscriber) -> GDNSjsonPublisher {
let endpoint: GDNSjsonEndpoint
Expand All @@ -203,25 +203,25 @@ extension RestClient where Server == GoogleDnsServer {
} catch {
return GDNSjsonPublisher(Future.failure(HttpError.failedConstructRequestParameters))
}

let adapter: AlamofireHTTPAdaptee<GoogleDNSOverJSONResponse,
GoogleDnsServer> = .init(.waitsForCombinePromise)
let future = self.cMakePublicRequest(for: endpoint, transport: adapter, subscriber: subscriber)
return future.eraseToAnyPublisher()
}

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func resolvedDomainName(in url: URL, _ subscriber: GDNSJsonClientSubscriber) -> AnyPublisher<URL, DnsError> {
return url.cHttpHost
.mapError { _ -> HttpError in
return .failedConstructRequestParameters
}
.flatMap { self.cGetIPaddress(ofDomain: $0, subscriber) }
.map { $0.ipAddress}
.mapError { (kitErr) -> DnsError in
return .httpError(kitErr)
}
.flatMap { url.cUpdatedHost(with: $0) }
.eraseToAnyPublisher()
.mapError { _ -> HttpError in
return .failedConstructRequestParameters
}
.flatMap { self.cGetIPaddress(ofDomain: $0, subscriber) }
.map { $0.ipAddress}
.mapError { (kitErr) -> DnsError in
return .httpError(kitErr)
}
.flatMap { url.cUpdatedHost(with: $0) }
.eraseToAnyPublisher()
}
}
Loading
Loading