diff --git a/Foundation/Source/Sources/Source/Source.swift b/Foundation/Source/Sources/Source/Source.swift index c6189df..909483e 100644 --- a/Foundation/Source/Sources/Source/Source.swift +++ b/Foundation/Source/Sources/Source/Source.swift @@ -138,7 +138,7 @@ public class Source: ObservableObject, Identifiable, Equatable { DispatchQueue.global().async { self.populateInstanceInfo() } - DispatchQueue.global().async { + DispatchQueue.global().asyncAfter(deadline: .now() + 2) { self.timeline.requestUpdate(direction: .newer) self.bookmark.reloadBookmark() self.notifications.fetchNotification(direction: .new) diff --git a/Kimis.xcodeproj/project.pbxproj b/Kimis.xcodeproj/project.pbxproj index 23f93cd..ff87ae0 100644 --- a/Kimis.xcodeproj/project.pbxproj +++ b/Kimis.xcodeproj/project.pbxproj @@ -1857,8 +1857,8 @@ CODE_SIGN_ENTITLEMENTS = Kimis/Kimis.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10; - DEVELOPMENT_TEAM = M4Z5DVY94F; + CURRENT_PROJECT_VERSION = 20; + DEVELOPMENT_TEAM = 6CMYQQFFT8; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Kimis/Info.plist; @@ -1875,7 +1875,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.kimis.inhouse; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1896,7 +1896,7 @@ CODE_SIGN_ENTITLEMENTS = Kimis/Kimis.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = 6CMYQQFFT8; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; GENERATE_INFOPLIST_FILE = YES; @@ -1914,7 +1914,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = as.wiki.qaq.kimis; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Kimis/App/AppDelegate.swift b/Kimis/App/AppDelegate.swift index f4eee8f..c9243fb 100644 --- a/Kimis/App/AppDelegate.swift +++ b/Kimis/App/AppDelegate.swift @@ -41,12 +41,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - func application(_: UIApplication, didDiscardSceneSessions _: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - func applicationWillTerminate(_ application: UIApplication) { print("[*] \(application) \(#function)") // clean up diff --git a/Kimis/App/AppTask.swift b/Kimis/App/AppTask.swift index 1c3b77f..eda1e9a 100644 --- a/Kimis/App/AppTask.swift +++ b/Kimis/App/AppTask.swift @@ -6,7 +6,6 @@ // import BackgroundTasks -import Bugsnag import Foundation import UIKit @@ -24,9 +23,6 @@ enum AppTask: String, CaseIterable, Codable { print("[*] success fully scheduled task \(AppTask.fetchNotifications.rawValue)") } catch { print("[?] could not schedule app refresh: \(error)") - #if !DEBUG - Bugsnag.notifyError(error) - #endif } } diff --git a/Kimis/App/SceneDelegate.swift b/Kimis/App/SceneDelegate.swift index 0dd955f..c69e419 100644 --- a/Kimis/App/SceneDelegate.swift +++ b/Kimis/App/SceneDelegate.swift @@ -5,6 +5,7 @@ // Created by Lakr Aream on 2022/11/14. // +import AVFoundation import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -29,32 +30,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window.makeKeyAndVisible() } - func sceneDidDisconnect(_: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - func sceneDidBecomeActive(_: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - Account.shared.requestStatusUpdate() - } - - func sceneWillResignActive(_: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_: UIScene) { - // Called as the scene transitions from the foreground to the background. - // 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. + do { + _ = try AVAudioSession.sharedInstance().setCategory( + .playback, + mode: .default, + options: .mixWithOthers + ) + } catch { + print(error.localizedDescription) + } } } diff --git a/Kimis/Backend/Account/Account.swift b/Kimis/Backend/Account/Account.swift index 433d707..20c06dc 100644 --- a/Kimis/Backend/Account/Account.swift +++ b/Kimis/Backend/Account/Account.swift @@ -94,6 +94,7 @@ final class Account { func delete(receiptID: LoginChallengeReceipt.ID) { assert(Thread.isMainThread) accounts.removeValue(forKey: receiptID) + cleanUp() if source?.receiptId == receiptID { deactivateCurrent() } } diff --git a/Kimis/Backend/TextParser/Render/TextParser+Username.swift b/Kimis/Backend/TextParser/Render/TextParser+Username.swift index cc6ac12..28b5b12 100644 --- a/Kimis/Backend/TextParser/Render/TextParser+Username.swift +++ b/Kimis/Backend/TextParser/Render/TextParser+Username.swift @@ -9,12 +9,7 @@ import Foundation import UIKit extension TextParser { - func replaceAttributeForUsername(with string: NSMutableAttributedString, defaultHost: String? = nil) { - replaceAttributeForUnifiedUsername(with: string) - replaceAttributeForSimpleUsername(with: string, defaultHost: defaultHost) - } - - private func replaceAttributeForSimpleUsername(with string: NSMutableAttributedString, defaultHost: String? = nil) { + func replaceAttributeForSimpleUsername(with string: NSMutableAttributedString, defaultHost: String? = nil) { enumeratedModifyingWithRegex(withinString: string, matching: .username) { string in guard !string.attributes.keys.contains(.link) else { return nil } var username = string.string @@ -28,7 +23,7 @@ extension TextParser { } } - private func replaceAttributeForUnifiedUsername(with string: NSMutableAttributedString) { + func replaceAttributeForUnifiedUsername(with string: NSMutableAttributedString) { enumeratedModifyingWithRegex(withinString: string, matching: .unifiedUsername) { string in guard !string.attributes.keys.contains(.link) else { return nil } guard let message = string.string.base64Encoded else { return nil } diff --git a/Kimis/Backend/TextParser/TextParser+RegEx.swift b/Kimis/Backend/TextParser/TextParser+RegEx.swift index ff366da..14af214 100644 --- a/Kimis/Backend/TextParser/TextParser+RegEx.swift +++ b/Kimis/Backend/TextParser/TextParser+RegEx.swift @@ -10,10 +10,10 @@ import Foundation extension TextParser { enum RegEx: String, CaseIterable { case username = #"(?<=(^|\s))@([A-Za-z0-9_])+(?=($|\s))"# - case unifiedUsername = #"(?<=(^|\s))@([A-Za-z0-9_])+?@([A-Za-z0-9\-\.]+)\.([A-Za-z]+)(?=($|\s))"# + case unifiedUsername = #"@([A-Za-z0-9_])+?@([A-Za-z0-9\-\.]+)\.([A-Za-z]+)(?=($|\s))"# case emoji = #":[A-Za-z0-9._]+:"# - case hashtag = #"#[\u4E00-\u9FCCA-Za-z0-9\.]+"# + case hashtag = #"#[\u4E00-\u9FCCA-Za-z0-9_\.]+"# case repliesMentionPrefix = #"^((@([A-Za-z0-9_])+?@([A-Za-z0-9\-\.]+)\.([A-Za-z]+)) )+"# case mail = #"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}"# case link = #"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9]{1,6}\b([-a-zA-Z0-9@:%_\+.~#?&,//=]*)"# diff --git a/Kimis/Backend/TextParser/TextParser.swift b/Kimis/Backend/TextParser/TextParser.swift index e7cce6e..8c3f303 100644 --- a/Kimis/Backend/TextParser/TextParser.swift +++ b/Kimis/Backend/TextParser/TextParser.swift @@ -94,8 +94,9 @@ extension TextParser { attributeFullFill(string) replaceAllAttributeForMarkdownSyntax(with: string) replaceAttributeForHashtag(with: string) + replaceAttributeForUnifiedUsername(with: string) replaceAttributeForEmails(with: string) - replaceAttributeForUsername(with: string, defaultHost: defaultHost) + replaceAttributeForSimpleUsername(with: string, defaultHost: defaultHost) replaceAttributeForLinks(with: string) replaceTinyEmoji(from: string, defaultHost: defaultHost) while string.string.hasPrefix(" ") { string.deleteCharacters(in: NSRange(location: 0, length: 1)) } diff --git a/Kimis/Info.plist b/Kimis/Info.plist index 8db5614..e5a4125 100644 --- a/Kimis/Info.plist +++ b/Kimis/Info.plist @@ -32,6 +32,7 @@ UIBackgroundModes + audio fetch processing diff --git a/Kimis/Interface/Component/Misc/TextView.swift b/Kimis/Interface/Component/Misc/TextView.swift index 6ca79dd..90d7f36 100644 --- a/Kimis/Interface/Component/Misc/TextView.swift +++ b/Kimis/Interface/Component/Misc/TextView.swift @@ -51,6 +51,7 @@ class TextView: SubviewAttachingTextView, UITextViewDelegate { textContainerInset = .zero textContainer.lineBreakMode = .byTruncatingTail isScrollEnabled = false + clipsToBounds = false textDragInteraction?.isEnabled = false diff --git a/Kimis/Interface/Component/NoteTableView/NoteTableView/NoteTableView.swift b/Kimis/Interface/Component/NoteTableView/NoteTableView/NoteTableView.swift index b3d7b43..b015e94 100644 --- a/Kimis/Interface/Component/NoteTableView/NoteTableView/NoteTableView.swift +++ b/Kimis/Interface/Component/NoteTableView/NoteTableView/NoteTableView.swift @@ -59,6 +59,8 @@ class NoteTableView: TableView { if layoutWidth != bounds.width { renderVisibleCellAndUpdate() layoutWidth = bounds.width + } else { + reconfigureVisibleCells() } super.layoutSubviews() } @@ -85,8 +87,13 @@ class NoteTableView: TableView { defer { if locked { dataUpdateLock.unlock() } } - let visibleIndexPaths = indexPathsForVisibleRows ?? [] beginUpdates() + reconfigureVisibleCells() + endUpdates() + } + + func reconfigureVisibleCells() { + let visibleIndexPaths = indexPathsForVisibleRows ?? [] for indexPath in visibleIndexPaths { guard let cell = cellForRow(at: indexPath) as? NoteCell else { continue @@ -96,6 +103,5 @@ class NoteTableView: TableView { } cell.load(data: context) } - endUpdates() } } diff --git a/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Builder.swift b/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Builder.swift index 290af1b..ee36a84 100644 --- a/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Builder.swift +++ b/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Builder.swift @@ -59,6 +59,7 @@ extension TimelineTableView { assert(DispatchQueue.isCurrent(_dataBuildQueue)) var insertContentHeight = false + var resetToTop = false let ctx = Self.translate(rawNodes: patch.nodes) switch patch.kind { @@ -67,6 +68,7 @@ extension TimelineTableView { order: patch.order, context: ctx ) + resetToTop = true case .insert: patchContainer = .init( order: patch.order, @@ -90,7 +92,8 @@ extension TimelineTableView { let item = DispatchWorkItem { self.applyDataSource( result, - insertContentHeight: insertContentHeight + insertContentHeight: insertContentHeight, + resetToTop: resetToTop ) } DispatchQueue.main.asyncAndWait(execute: item) @@ -180,7 +183,7 @@ extension TimelineTableView { .count let newNotesCount = currentNotes - previousNotes if newNotesCount > 0 { - guider?.setCountMax(newNotesCount) + guider?.setCountMax(newNotesCount, appending: true) presentNewItemGuider() } } diff --git a/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Guider.swift b/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Guider.swift index 361cf45..1305495 100644 --- a/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Guider.swift +++ b/Kimis/Interface/Component/NoteTableView/TimelineTableView/TimelineTableView+Guider.swift @@ -88,8 +88,8 @@ extension NoteTableView { get { _realCount } } - func setCountMax(_ cnt: Int) { - _realCount = cnt + func setCountMax(_ cnt: Int, appending: Bool = false) { + _realCount = cnt + (appending ? _realCount : 0) } override init(frame _: CGRect) { diff --git a/Kimis/Interface/Component/NoteView/Attachments/NoteAttachmentView+Video.swift b/Kimis/Interface/Component/NoteView/Attachments/NoteAttachmentView+Video.swift index 769b52a..ff1191d 100644 --- a/Kimis/Interface/Component/NoteView/Attachments/NoteAttachmentView+Video.swift +++ b/Kimis/Interface/Component/NoteView/Attachments/NoteAttachmentView+Video.swift @@ -166,7 +166,6 @@ private final class VideoPreviewLoopedPlayerView: UIView { } func prepareVideo(_ videoURL: URL) { - requestMixWithOthers() let playerItem = AVPlayerItem(url: videoURL) self.queuePlayer = AVQueuePlayer(playerItem: playerItem) self.playerLayer = AVPlayerLayer(player: self.queuePlayer) @@ -211,22 +210,4 @@ private final class VideoPreviewLoopedPlayerView: UIView { deinit { unload() } - - func requestMixWithOthers() { - // aviod block main thread when syscall is blocking by AirPlay using some kinda HomePod - DispatchQueue.global().async { - do { -// this may access microphone with pop up -// guard !AVAudioSession.sharedInstance().categoryOptions.contains(.mixWithOthers) -// else { return } - _ = try AVAudioSession.sharedInstance().setCategory( - .playback, - mode: .default, - options: .mixWithOthers - ) - } catch { - print(error.localizedDescription) - } - } - } } diff --git a/Kimis/Interface/Component/NoteView/NotePreview.swift b/Kimis/Interface/Component/NoteView/NotePreview.swift index 0fb5de6..676beff 100644 --- a/Kimis/Interface/Component/NoteView/NotePreview.swift +++ b/Kimis/Interface/Component/NoteView/NotePreview.swift @@ -10,6 +10,8 @@ import Source import UIKit class NotePreview: UIView { + // # line 325, reason icon height = text height + static let hintTextLimit = 1 // <- must be 1 static let userTextLimit = 2 static let mainTextLimit = 8 static let verticalSpacing: CGFloat = 8 @@ -43,7 +45,7 @@ class NotePreview: UIView { init() { super.init(frame: .zero) - previewReason.textContainer.maximumNumberOfLines = Self.userTextLimit + previewReason.textContainer.maximumNumberOfLines = Self.hintTextLimit userText.textContainer.maximumNumberOfLines = Self.userTextLimit mainText.textContainer.maximumNumberOfLines = Self.mainTextLimit @@ -315,7 +317,7 @@ extension NotePreview.Snapshot { var previewResaonRect: CGRect = .zero let previewReasonHeight = previewReasonText - .measureHeight(usingWidth: contentWidth, lineLimit: NotePreview.userTextLimit) + .measureHeight(usingWidth: contentWidth, lineLimit: NotePreview.hintTextLimit) previewResaonRect = .init( x: contentLeftAlign, y: 0, diff --git a/Kimis/Interface/Controller/SettingController/SettingController.swift b/Kimis/Interface/Controller/SettingController/SettingController.swift index 79c2c54..57d2703 100644 --- a/Kimis/Interface/Controller/SettingController/SettingController.swift +++ b/Kimis/Interface/Controller/SettingController/SettingController.swift @@ -125,6 +125,9 @@ class SettingController: ViewController { } action: { tableView in let alert = UIAlertController(title: "⚠️", message: "Are you sure you want to sign out?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Sign Out", style: .destructive) { _ in + if let id = Account.shared.source?.receiptId { + Account.shared.delete(receiptID: id) + } Account.shared.deactivateCurrent() }) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))