Skip to content

Commit

Permalink
feat: Upload webpage with readability and other UX improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
rachit-shah committed May 18, 2024
1 parent e3d6741 commit 02bac4a
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 15 deletions.
47 changes: 47 additions & 0 deletions AetherVoice.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
6036DBE82BF95CFD00A42D1A /* Readability in Frameworks */ = {isa = PBXBuildFile; productRef = 6036DBE72BF95CFD00A42D1A /* Readability */; };
6040521C2B40414E00100DC5 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 6040521B2B40414E00100DC5 /* README.md */; };
604052202B405DC700100DC5 /* AWSSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6040521F2B405DC700100DC5 /* AWSSettingsView.swift */; };
604052222B405DF200100DC5 /* GCPSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604052212B405DF200100DC5 /* GCPSettingsView.swift */; };
Expand All @@ -18,6 +19,7 @@
604A91482B3CB36200836DAF /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604A91472B3CB36200836DAF /* RuntimeError.swift */; };
605FC1B52BE72C030086987A /* AzureSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605FC1B42BE72C030086987A /* AzureSettingsView.swift */; };
605FC1B72BE73D890086987A /* AzureVoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605FC1B62BE73D890086987A /* AzureVoice.swift */; };
606D3B9D2BF943680047994C /* URLInputSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606D3B9C2BF943680047994C /* URLInputSheet.swift */; };
607283C82B3875D7002B5DAF /* AetherVoiceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607283C72B3875D7002B5DAF /* AetherVoiceApp.swift */; };
607283CA2B3875D7002B5DAF /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607283C92B3875D7002B5DAF /* Persistence.swift */; };
607283CD2B3875D7002B5DAF /* AetherVoice.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 607283CB2B3875D7002B5DAF /* AetherVoice.xcdatamodeld */; };
Expand Down Expand Up @@ -54,6 +56,7 @@
604A91472B3CB36200836DAF /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = "<group>"; };
605FC1B42BE72C030086987A /* AzureSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzureSettingsView.swift; sourceTree = "<group>"; };
605FC1B62BE73D890086987A /* AzureVoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzureVoice.swift; sourceTree = "<group>"; };
606D3B9C2BF943680047994C /* URLInputSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLInputSheet.swift; sourceTree = "<group>"; };
607283C42B3875D7002B5DAF /* AetherVoice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AetherVoice.app; sourceTree = BUILT_PRODUCTS_DIR; };
607283C72B3875D7002B5DAF /* AetherVoiceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AetherVoiceApp.swift; sourceTree = "<group>"; };
607283C92B3875D7002B5DAF /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -92,6 +95,7 @@
604A8F322B3C563F00836DAF /* AWSCognitoIdentity in Frameworks */,
604052A82B40892B00100DC5 /* GoogleAPIClientForREST_Texttospeech in Frameworks */,
607283E42B39B2A5002B5DAF /* UniformTypeIdentifiers.framework in Frameworks */,
6036DBE82BF95CFD00A42D1A /* Readability in Frameworks */,
604052AE2B42EF8400100DC5 /* AppKit.framework in Frameworks */,
604A90842B3C564700836DAF /* AWSPolly in Frameworks */,
607283E02B39B074002B5DAF /* MobileCoreServices.framework in Frameworks */,
Expand All @@ -101,6 +105,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
606D3B9B2BF943540047994C /* Sheets */ = {
isa = PBXGroup;
children = (
606D3B9C2BF943680047994C /* URLInputSheet.swift */,
);
path = Sheets;
sourceTree = "<group>";
};
607283BB2B3875D7002B5DAF = {
isa = PBXGroup;
children = (
Expand All @@ -127,6 +139,7 @@
607283FC2B3A0CE8002B5DAF /* Utilities */,
607283E92B39BE90002B5DAF /* CoreData */,
607283E82B39BE8C002B5DAF /* Services */,
606D3B9B2BF943540047994C /* Sheets */,
607283E72B39BE84002B5DAF /* ViewModels */,
607283E62B39BE76002B5DAF /* Views */,
607283E52B39BE67002B5DAF /* Models */,
Expand Down Expand Up @@ -251,6 +264,7 @@
604A8F312B3C563F00836DAF /* AWSCognitoIdentity */,
604A90832B3C564700836DAF /* AWSPolly */,
604052A72B40892B00100DC5 /* GoogleAPIClientForREST_Texttospeech */,
6036DBE72BF95CFD00A42D1A /* Readability */,
);
productName = CloudReader;
productReference = 607283C42B3875D7002B5DAF /* AetherVoice.app */;
Expand Down Expand Up @@ -284,6 +298,9 @@
604A8E922B3C563D00836DAF /* XCRemoteSwiftPackageReference "aws-sdk-swift" */,
604052232B4085DF00100DC5 /* XCRemoteSwiftPackageReference "google-api-swift-client" */,
604052282B4088AF00100DC5 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */,
6036DBE02BF9552600A42D1A /* XCRemoteSwiftPackageReference "SwiftSoup" */,
6036DBE32BF958BC00A42D1A /* XCRemoteSwiftPackageReference "ReadabilityKit" */,
6036DBE62BF95CFD00A42D1A /* XCRemoteSwiftPackageReference "read-swift" */,
);
productRefGroup = 607283C52B3875D7002B5DAF /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -316,6 +333,7 @@
files = (
607283EF2B39BEDE002B5DAF /* DocumentReaderView.swift in Sources */,
604052AA2B42DFB400100DC5 /* SplashView.swift in Sources */,
606D3B9D2BF943680047994C /* URLInputSheet.swift in Sources */,
6072840A2B3AE68A002B5DAF /* AmazonPollySynthesizer.swift in Sources */,
607283C82B3875D7002B5DAF /* AetherVoiceApp.swift in Sources */,
60DFF5E92B3EF375004E098C /* KeychainWrapper.swift in Sources */,
Expand Down Expand Up @@ -577,6 +595,30 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
6036DBE02BF9552600A42D1A /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.7.2;
};
};
6036DBE32BF958BC00A42D1A /* XCRemoteSwiftPackageReference "ReadabilityKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/exyte/ReadabilityKit.git";
requirement = {
branch = master;
kind = branch;
};
};
6036DBE62BF95CFD00A42D1A /* XCRemoteSwiftPackageReference "read-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ttscoff/read-swift.git";
requirement = {
branch = master;
kind = branch;
};
};
604052232B4085DF00100DC5 /* XCRemoteSwiftPackageReference "google-api-swift-client" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/googleapis/google-api-swift-client.git";
Expand Down Expand Up @@ -604,6 +646,11 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
6036DBE72BF95CFD00A42D1A /* Readability */ = {
isa = XCSwiftPackageProductDependency;
package = 6036DBE62BF95CFD00A42D1A /* XCRemoteSwiftPackageReference "read-swift" */;
productName = Readability;
};
604052A72B40892B00100DC5 /* GoogleAPIClientForREST_Texttospeech */ = {
isa = XCSwiftPackageProductDependency;
package = 604052282B4088AF00100DC5 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"originHash" : "e1480913fee0377ac9ce9e98475affed68b16b8984e458867e554ba3f77d28e0",
"pins" : [
{
"identity" : "aws-crt-swift",
Expand Down Expand Up @@ -72,6 +73,33 @@
"version" : "3.2.0"
}
},
{
"identity" : "ji",
"kind" : "remoteSourceControl",
"location" : "https://github.com/honghaoz/Ji",
"state" : {
"revision" : "a37a310cc6aaf999de4bee953a9680bf9b629200",
"version" : "5.1.0"
}
},
{
"identity" : "read-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ttscoff/read-swift.git",
"state" : {
"branch" : "master",
"revision" : "55db904566502acac7fbe3fd9fe4c7f416a49afe"
}
},
{
"identity" : "readabilitykit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/exyte/ReadabilityKit.git",
"state" : {
"branch" : "master",
"revision" : "bab3bf8f62f08af2bfc78e0c4f7c9e6c6ef51a11"
}
},
{
"identity" : "smithy-swift",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -117,6 +145,15 @@
"version" : "2.62.0"
}
},
{
"identity" : "swiftsoup",
"kind" : "remoteSourceControl",
"location" : "https://github.com/scinfu/SwiftSoup.git",
"state" : {
"revision" : "028487d4a8a291b2fe1b4392b5425b6172056148",
"version" : "2.7.2"
}
},
{
"identity" : "xmlcoder",
"kind" : "remoteSourceControl",
Expand All @@ -127,5 +164,5 @@
}
}
],
"version" : 2
"version" : 3
}
16 changes: 16 additions & 0 deletions AetherVoice/CoreData/Persistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ class PersistenceController {
print("Failed to save document: \(error)")
}
}

func deleteDocument(_ appDocument: AppDocument) {
let fetchRequest: NSFetchRequest<Document> = Document.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %@", appDocument.id as CVarArg)

do {
let documentEntities = try container.viewContext.fetch(fetchRequest)
for document in documentEntities {
container.viewContext.delete(document)
}
try container.viewContext.save()
} catch {
// Handle the error appropriately
print("Failed to delete document: \(error)")
}
}

func fetchDocuments() -> [AppDocument] {
let fetchRequest: NSFetchRequest<Document> = Document.fetchRequest()
Expand Down
2 changes: 1 addition & 1 deletion AetherVoice/Models/AppDocument.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct AppDocument: Identifiable {
struct AppDocument: Hashable, Identifiable {
var id: UUID
var title: String
var content: String
Expand Down
70 changes: 70 additions & 0 deletions AetherVoice/Sheets/URLInputSheet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import SwiftUI

struct URLInputSheet: View {
@Binding var isPresented: Bool
@State private var urlStrings: [String] = [""]
var fetchContent: ([String]) -> Void

var body: some View {
NavigationView {
VStack(spacing: 20) {
ScrollView {
ForEach(0..<urlStrings.count, id: \.self) { index in
HStack {
TextField("http://example.com", text: $urlStrings[index])
.textFieldStyle(RoundedBorderTextFieldStyle())
.disableAutocorrection(true)
.padding(.horizontal)

if urlStrings.count > 1 {
Button(action: {
urlStrings.remove(at: index)
}) {
Image(systemName: "minus.circle.fill")
.foregroundColor(.red)
}
}
}
.padding(.vertical, 5)
}
}

HStack(spacing: 20) {
Button(action: {
urlStrings.append("")
}) {
Image(systemName: "plus.circle.fill")
.padding(.horizontal, 16)
}
.background(Color.green)
.foregroundColor(.white)
.clipShape(Capsule())

Button(action: {
fetchContent(urlStrings.filter { !$0.isEmpty })
isPresented = false
}) {
Image(systemName: "checkmark.circle.fill")
.padding(.horizontal, 16)
}
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
.padding(.horizontal)

Spacer()
}
.padding()
.navigationTitle("Enter URLs")
#if os(iOS)
.navigationBarItems(trailing: Button(action: {
isPresented = false
}) {
Image(systemName: "xmark")
.foregroundColor(.gray)
})
#endif
}
}
}
53 changes: 52 additions & 1 deletion AetherVoice/ViewModels/DocumentListViewModel.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Combine
import Foundation
import PDFKit
import Readability
import UniformTypeIdentifiers

class DocumentListViewModel: ObservableObject {
@MainActor class DocumentListViewModel: ObservableObject {
@Published var documents: [AppDocument] = []
var synthesizerDict: [TTSService: SpeechSynthesizerProtocol] = [TTSService: SpeechSynthesizerProtocol]()
private let persistenceController = PersistenceController.shared
Expand All @@ -17,6 +18,18 @@ class DocumentListViewModel: ObservableObject {
persistenceController.saveDocument(appDocument)
fetchDocuments() // Refresh the documents list
}

func deleteDocument(_ document: AppDocument) {
persistenceController.deleteDocument(document)
fetchDocuments() // Refresh the document list after deletion
}

func deleteDocuments(at offsets: IndexSet) {
for index in offsets {
let document = documents[index]
deleteDocument(document)
}
}

func fetchDocuments() {
documents = persistenceController.fetchDocuments()
Expand Down Expand Up @@ -104,6 +117,44 @@ class DocumentListViewModel: ObservableObject {
print("Unsupported file type")
}
}

func fetchContent(at urlString: String) {
print("URL: \(urlString)")
guard let url = URL(string: urlString) else {
print("Invalid url \(urlString)")
return
}

let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
// Handle error
print("Error fetching content: \(error)")
return
}

guard let data = data, let html = String(data: data, encoding: .utf8) else {
print("No data returned from url \(urlString)")
return
}

do {
// Parse the webpage content
let readability = Readability(html: html)
let started = readability.start()
if started {
let title = try readability.articleTitle?.text()
let data = try readability.articleContent?.text()
if (title != nil && data != nil) {
let newDocument = AppDocument(title: title!, content: data!)
self.saveDocument(newDocument)
}
}
} catch {
print("Error parsing html \(html)")
}
}
task.resume()
}

func processPlainTextDocument(at url: URL) {
if let contents = try? String(contentsOf: url) {
Expand Down
Loading

0 comments on commit 02bac4a

Please sign in to comment.