Add Spotlight support for iOS

This commit is contained in:
2020-12-21 16:38:22 +01:00
parent 01b62f667a
commit 7843814429
12 changed files with 139 additions and 43 deletions

View File

@@ -65,7 +65,7 @@ class CircularLetterViewModel internal constructor(
viewModelScope.launch {
isNotUpdating = false
if (!circularRepository.updateCirculars(false).second) {
if (circularRepository.updateCirculars(false).second == -1) {
showMessage.postValue(true)
}

View File

@@ -94,7 +94,7 @@ class PollWork(appContext: Context, workerParams: WorkerParameters) :
val circularRepository = AndroidCircularRepository.getInstance(applicationContext)
val result = circularRepository.updateCirculars()
if (!result.second)
if (result.second == -1)
return@coroutineScope Result.retry()
val newCirculars = result.first

View File

@@ -36,6 +36,7 @@
95CA31B7255C1EE000AC095B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95CA31B6255C1EE000AC095B /* Preview Assets.xcassets */; };
95CA31C0255C28C300AC095B /* CircularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA31BF255C28C300AC095B /* CircularView.swift */; };
95DB71AB258A1C1500A78033 /* CarteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95DB71AA258A1C1500A78033 /* CarteView.swift */; };
95F6666E2590D712006DE74F /* URLUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95F6666D2590D712006DE74F /* URLUtils.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -86,6 +87,7 @@
95CA31B8255C1EE000AC095B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
95CA31BF255C28C300AC095B /* CircularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularView.swift; sourceTree = "<group>"; };
95DB71AA258A1C1500A78033 /* CarteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarteView.swift; sourceTree = "<group>"; };
95F6666D2590D712006DE74F /* URLUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLUtils.swift; sourceTree = "<group>"; };
976621FBDDCA894FD23FBA8B /* Pods-circolapp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-circolapp.release.xcconfig"; path = "Target Support Files/Pods-circolapp/Pods-circolapp.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -193,6 +195,7 @@
95906F4C257510370060F5D0 /* GoogleService-Info.plist */,
95906F47257506360060F5D0 /* Assets.xcassets */,
95CA31B5255C1EE000AC095B /* Preview Content */,
95F6666D2590D712006DE74F /* URLUtils.swift */,
);
path = circolapp;
sourceTree = "<group>";
@@ -382,6 +385,7 @@
955B7A0E257D098C0091B1F9 /* SearchBar.swift in Sources */,
9554BDB8257E498F00D8925B /* OnboardingView.swift in Sources */,
950C1788258E5BD300B2DBFE /* PhoneView.swift in Sources */,
95F6666E2590D712006DE74F /* URLUtils.swift in Sources */,
95C46A51255D3A34007A75E5 /* CircularViewModel.swift in Sources */,
95B4CE142588BC890090D5E8 /* SettingsView.swift in Sources */,
);

View File

@@ -71,14 +71,8 @@ extension AppDelegate : UNUserNotificationCenterDelegate {
}
// Handle new circular notification
iOSRepository.getCircularRepository().updateCirculars(returnNewCirculars: false, completionHandler:
{ result, error in
if let errorReal = error {
print(errorReal.localizedDescription)
}
})
let repository = iOSRepository.getCircularRepository()
iOSRepository.updateCirculars(circularRepository: repository)
// UI is updated automatically
completionHandler([])
@@ -96,12 +90,8 @@ extension AppDelegate : UNUserNotificationCenterDelegate {
return
}
iOSRepository.getCircularRepository().updateCirculars(returnNewCirculars: false, completionHandler:
{ result, error in
if let errorReal = error {
print(errorReal.localizedDescription)
}
})
let repository = iOSRepository.getCircularRepository()
iOSRepository.updateCirculars(circularRepository: repository)
completionHandler()
}

View File

@@ -17,6 +17,8 @@
*/
import Foundation
import CoreSpotlight
import MobileCoreServices
import Shared
class iOSRepository {
@@ -30,4 +32,64 @@ class iOSRepository {
let serverAPI = iOSServerApi.instance.serverAPI
return CircularRepository(circularDao: getCircularDao(), serverAPI: serverAPI)
}
public static func updateCirculars(circularRepository: CircularRepository) {
circularRepository.updateCirculars(returnNewCirculars: true, completionHandler:
{ result, error in
if let errorReal = error {
print(errorReal.localizedDescription)
return
}
// Database was resetted, remove all circulars from spotlight
if result?.second == 1 {
deleteAllFromSpotlight(reindex: false, serverID: -1)
}
// Index circulars
for circular in result!.first as! Array<Circular> {
indexToSpotlight(circular: circular)
}
})
}
public static func indexAllToSpotlight(serverID: Int) {
let circulars = getCircularDao().getCirculars(school: Int32(serverID))
for circular in circulars {
indexToSpotlight(circular: circular)
}
}
public static func indexToSpotlight(circular: Circular) {
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String)
attributeSet.title = "Circular number \(circular.id)"
attributeSet.contentDescription = circular.name
attributeSet.identifier = "\(circular.id)"
let item = CSSearchableItem(uniqueIdentifier: "\(circular.id)", domainIdentifier: "net.underdesk.circolapp", attributeSet: attributeSet)
item.expirationDate = Date.distantFuture
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("Indexing error: \(error.localizedDescription)")
}
}
}
public static func deleteAllFromSpotlight(reindex: Bool, serverID: Int) {
CSSearchableIndex.default().deleteAllSearchableItems(completionHandler: {
error in
if let errorReal = error {
print(errorReal.localizedDescription)
return
}
if reindex {
DispatchQueue.main.async {
indexAllToSpotlight(serverID: serverID)
}
}
})
}
}

View File

@@ -45,10 +45,15 @@ class iOSServerApi {
}
func changeServer(serverID: Int) {
// Change server provider
let serverID = UserDefaults.standard.integer(forKey: schoolKey)
let server = serverCompanion.getServer(serverID: Int32(serverID))
serverAPI = ServerAPI(serverName: server)
// Reset spotlight indexed items
iOSRepository.deleteAllFromSpotlight(reindex: true, serverID: serverID)
// Change FCM topic
let nullableOldTopic = UserDefaults.standard.string(forKey: topicKey)
let newTopic = serverCompanion.getServerTopic(serverID: Int32(serverID))
if (nullableOldTopic == nil || nullableOldTopic != newTopic) {
@@ -63,11 +68,6 @@ class iOSServerApi {
UserDefaults.standard.set(newTopic, forKey: topicKey)
}
CircularRepository(circularDao: iOSRepository.getCircularDao(), serverAPI: serverAPI).updateCirculars(returnNewCirculars: false, completionHandler:
{ result, error in
if let errorReal = error {
print(errorReal.localizedDescription)
}
})
iOSRepository.updateCirculars(circularRepository: CircularRepository(circularDao: iOSRepository.getCircularDao(), serverAPI: serverAPI))
}
}

View File

@@ -6,6 +6,7 @@
//
import UIKit
import CoreSpotlight
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
@@ -27,6 +28,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
self.window = window
window.makeKeyAndVisible()
}
if let userActivity = connectionOptions.userActivities.first {
handleUserActivity(userActivity: userActivity)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
// Called when the user open an entry from Spotlight
handleUserActivity(userActivity: userActivity)
}
func sceneDidDisconnect(_ scene: UIScene) {
@@ -76,4 +86,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
func handleUserActivity(userActivity: NSUserActivity) {
if userActivity.activityType == CSSearchableItemActionType {
if let uniqueIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
guard let circularID = Int64(uniqueIdentifier) else { return }
let schoolID = UserDefaults.standard.integer(forKey: "school")
let circularDao = iOSRepository.getCircularDao()
let circular = circularDao.getCircular(id: circularID, school: Int32(schoolID))
URLUtils.openUrl(url: circular.url)
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Circolapp
* Copyright (C) 2019-2020 Matteo Schiff
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class URLUtils {
static func openUrl(url: String) {
guard let safeUrl = URL(string: url.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!) else { return }
UIApplication.shared.open(safeUrl)
}
}

View File

@@ -17,7 +17,6 @@
*/
import SwiftUI
import UIKit
import Shared
struct AttachmentView: View {
@@ -33,8 +32,7 @@ struct AttachmentView: View {
Spacer()
Button(action: {
guard let url = URL(string: attachmentUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!) else { return }
UIApplication.shared.open(url)
URLUtils.openUrl(url: attachmentUrl)
}) {
Image(systemName: "envelope.open.fill")
.resizable()

View File

@@ -17,7 +17,6 @@
*/
import SwiftUI
import UIKit
import Shared
struct CircularView: View {
@@ -49,8 +48,7 @@ struct CircularView: View {
if showDetail {
HStack {
Button(action: {
guard let url = URL(string: circular.url.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!) else { return }
UIApplication.shared.open(url)
URLUtils.openUrl(url: circular.url)
}) {
Image(systemName: "envelope.open.fill")
.resizable()

View File

@@ -33,7 +33,7 @@ class CircularViewModel: ObservableObject {
self.repository = repository
schoolID = UserDefaults.standard.integer(forKey: key)
updateCirculars()
iOSRepository.updateCirculars(circularRepository: repository)
userDefaultsObserver = UserDefaults.standard.observe(\.school, options: [.initial, .new], changeHandler: { (defaults, change) in
self.schoolID = change.newValue ?? 0
@@ -48,15 +48,6 @@ class CircularViewModel: ObservableObject {
userDefaultsObserver?.invalidate()
}
func updateCirculars() {
self.repository.updateCirculars(returnNewCirculars: false, completionHandler:
{ result, error in
if let errorReal = error {
print(errorReal.localizedDescription)
}
})
}
func startObservingCirculars() {
stopObserving()
circularWatcher = repository.circularDao.getCFlowCirculars(school: Int32(schoolID)).watch { circulars in

View File

@@ -6,12 +6,13 @@ class CircularRepository(
val circularDao: CircularDao,
private val serverAPI: ServerAPI
) {
suspend fun updateCirculars(returnNewCirculars: Boolean = true): Pair<List<Circular>, Boolean> {
suspend fun updateCirculars(returnNewCirculars: Boolean = true): Pair<List<Circular>, Int> {
var onlyNewCirculars = listOf<Circular>()
var errorCode = 0
val result = serverAPI.getCircularsFromServer()
if (result.second == ServerAPI.Companion.Result.ERROR)
return Pair(emptyList(), false)
return Pair(emptyList(), -1)
val oldCirculars = circularDao.getCirculars(serverAPI.serverID())
val newCirculars = result.first
@@ -19,6 +20,7 @@ class CircularRepository(
if (newCirculars.size != oldCirculars.size) {
if (newCirculars.size < oldCirculars.size) {
circularDao.deleteAll()
errorCode = 1
}
if (returnNewCirculars) {
@@ -31,6 +33,6 @@ class CircularRepository(
circularDao.insertAll(newCirculars)
}
return Pair(onlyNewCirculars, true)
return Pair(onlyNewCirculars, errorCode)
}
}