Integrate mobile sync auth and bookmark sign-in UI#757
Integrate mobile sync auth and bookmark sign-in UI#757AhmedNMahran wants to merge 10 commits intoquran:mainfrom
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #757 +/- ##
==========================================
- Coverage 40.92% 36.54% -4.39%
==========================================
Files 525 560 +35
Lines 20880 21505 +625
==========================================
- Hits 8546 7859 -687
- Misses 12334 13646 +1312 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
mohamede1945
left a comment
There was a problem hiding this comment.
Jazak Allah Khyrn for the implementation.
I've added comments where it's needed, please review them and let me know if there is anything I might have misunderstood.
Also, it would be great to create a doc with some of the details you added in the PR description about how to do local development, etc.
Lastly, the PR seems to lack unit tests, please add tests for the new services created. No need to test UI, just the business logic.
| @@ -0,0 +1,166 @@ | |||
| import Foundation | |||
| import UIKit | |||
| #if QURAN_SYNC | |||
There was a problem hiding this comment.
While this looks like a good guard to prevent leaking the new code into the app, but in our situation we don't need that. The drawback is that it complicates the implementation. But it's fine to ship with this new code, we just want to guard the UI and simplify this implementation.
| } | ||
|
|
||
| do { | ||
| _ = try await asyncFunction(for: authService.login()) |
There was a problem hiding this comment.
We need a way to hide these kotlin bridges (e.g. asyncFunction) behind the sync APIs. For example, creating a wrapper in the mobile-sync-spm package.
There was a problem hiding this comment.
Basically, I don't want clients to import KMPNativeCoroutinesAsync, MobileSync should be enough, IMO.
| authService.getAuthHeaders { headers, error in | ||
| if let error { | ||
| continuation.resume(throwing: AuthenticationClientError.clientIsNotAuthenticated(error)) | ||
| } else { | ||
| continuation.resume(returning: headers ?? [:]) | ||
| } | ||
| } |
There was a problem hiding this comment.
This looks like a missed asyncFunction, right?
| return authenticationState | ||
| } | ||
|
|
||
| public func logout() async throws { |
There was a problem hiding this comment.
FYI, we need to delete the previous impl in a separate PR inshaa'Allah.
Package.swift
Outdated
| let quranSyncCompilationConditions = ProcessInfo.processInfo.environment["QURAN_SYNC_SWIFT_ACTIVE_COMPILATION_CONDITIONS"] ?? "" | ||
| let quranSyncEnabled = ProcessInfo.processInfo.environment["QURAN_SYNC"] == "1" | ||
| || quranSyncCompilationConditions.split(separator: " ").contains("QURAN_SYNC") | ||
| let quranSyncSettings: [SwiftSetting] = quranSyncEnabled ? [ | ||
| .define("QURAN_SYNC"), | ||
| ] : [] | ||
|
|
||
| let settings = (enforceSwiftConcurrencyChecks ? swiftConcurrencySettings : []) + quranSyncSettings |
There was a problem hiding this comment.
We shouldn't do this, we should only manipulate the flag from the Xcode project.
| private let bookmarksRepository: any BookmarksRepository | ||
| private let syncService: SyncService? | ||
| private let legacyContext: NSManagedObjectContext? | ||
| private let subject = CurrentValueSubject<[PageBookmarkPersistenceModel], Never>([]) |
There was a problem hiding this comment.
We shouldn't have any local mutable state here, the object should be a wrapper around the mobile-sync corresponding object.
There was a problem hiding this comment.
We should also make this a Sendable struct instead of a class, please
| guard let legacyStack else { | ||
| preconditionFailure("CoreDataStack is required when Quran sync is disabled") | ||
| } | ||
| fallback = CoreDataPageBookmarkPersistence(stack: legacyStack) |
There was a problem hiding this comment.
The decision to use MobileSyncPageBookmarkPersistence or CoreDataPageBookmarkPersistence should be in the DI layer (i.e. Container.swift) not here.
|
|
||
| public func insertPageBookmark(_ page: Int) async throws { | ||
| if let syncService { | ||
| _ = try await asyncFunction(for: syncService.addBookmark(page: Int32(page))) |
There was a problem hiding this comment.
We also should make sure to have these asyncfunction wrappers in the spm package please
| public func insertPageBookmark(_ page: Int) async throws { | ||
| if let syncService { | ||
| _ = try await asyncFunction(for: syncService.addBookmark(page: Int32(page))) | ||
| syncService.triggerSync() |
There was a problem hiding this comment.
Should triggerSync be the responsibility of this adapter/wrapper? I would suggest mobile-sync to encapsulate these cases. Mobile app can still do the periodic, coming from the background sync but not data mutation syncs.
@ahmedre what do you think?
| MARKETING_VERSION = 1.20.3; | ||
| PRODUCT_BUNDLE_IDENTIFIER = com.quran.QuranEngineApp; | ||
| PRODUCT_NAME = "$(TARGET_NAME)"; | ||
| SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) $(QURAN_SYNC_SWIFT_ACTIVE_COMPILATION_CONDITIONS)"; |
There was a problem hiding this comment.
I don't think we should define another flag QURAN_SYNC_SWIFT_ACTIVE_COMPILATION_CONDITIONS, QURAN_SYNC should be enough
|
@mohamede1945 Jazak Allah Khayran for the detailed and instructed change request and comments,
|
|
@AhmedNMahran thanks for the changes! Just one major point:
I don't think I had requested this change, and I'm not in support of it for a few reasons:
For these reasons, I'd prefer that we revert this approach and keep the setup within the iOS client for now. |
Thank you @mohamede1945 I didn't mean that, so, the currently used ViewModel is the correct usage, iOS-only, consumed from iOS client. |
Summary
This PR integrates
mobile-sync-spmintoquran-iosbehind theQURAN_SYNCcompilation flag and adds the minimal UI needed to expose Quran.com sync in the app.It currently covers:
mobile-sync-spmMain changes
Sync package integration
mobile-sync-spmto0.0.4QURAN_SYNCQURAN_SYNCAuth integration
AuthenticationClientMobileSyncImplremains the app-facing auth implementation for syncMobileSyncSessionnow uses themobile-sync-spm 0.0.4wrapper pathQURAN_OAUTH_CLIENT_IDQURAN_OAUTH_ISSUER_URLQURAN_OAUTH_CLIENT_SECRETBookmark integration
mobile-sync-spmwhen sync is enabledUI
Tests
QuranProfileServicecoverage for missing-client login/logout behaviorLocal setup
Compile flag
Enable
QURAN_SYNCfor the example app target.Runtime environment
For the sync path:
QURAN_OAUTH_CLIENT_IDQURAN_OAUTH_ISSUER_URLOptional:
QURAN_OAUTH_CLIENT_SECRETQURAN_OAUTH_CLIENT_SECRETis only needed when the configured OAuth client is registered as a confidential client. If the environment supports a public PKCE client, leave it unset.Package defaults used by the sync path:
com.quran.oauth://callbackcom.quran.oauth://callbackopenid,offline_access,content,user,bookmark,sync,collection,reading_session,preference,noteWhen sync is not compiled in, the native fallback auth client reads the app-level OAuth configuration:
QURAN_OAUTH_CLIENT_IDQURAN_OAUTH_ISSUER_URLQURAN_OAUTH_REDIRECT_URLQURAN_OAUTH_SCOPESQURAN_OAUTH_CLIENT_SECRETManual verification
QURAN_SYNCNotes
QURAN_OAUTH_CLIENT_SECRETbecause that was required to match the currently working demo setup.