Skip to content

Conversation

@JinUng41
Copy link
Collaborator

@JinUng41 JinUng41 commented May 18, 2025

👻 PULL REQUEST

📄 작업 내용

각 QA 사항은 커밋 단위로 확인해주세요!

  • 뷰잇 관련 QA를 반영하였습니다.
  • 커뮤니티 관련 QA를 반영하였습니다.
  • 로딩뷰 관련 QA를 반영하였습니다.
  • 알림 관련 QA를 반영하였습니다.

💻 주요 코드 설명

📍 RawValue를 가지는 Enum을 확인가능한 방법으로 생성하기 위한 코드

  • RawValue를 가지는 Enum의 경우, rawValue 생성자를 이용하여 생성하고 있습니다.
  • 허나, 이는 어떠한 값일 때는 생성이 안되는지 파악이 불가합니다.
  • 따라서 타입 메서드를 이용하여, 최소한의 추적을 허용하면서 생성하는 방향성을 제안하고자 합니다.
TriggerType.ActivityNotification을 타입 메서드로 생성하기
enum ActivityNotification: String {
        case contentLike = "contentLiked"
        case commentLike = "commentLiked"
        case comment = "comment"
        case contentGhost = "contentGhost"
        case commentGhost = "commentGhost"
        case beGhost = "beGhost"
        case actingContinue = "actingContinue"
        case userBan = "userBan"
        case popularWriter = "popularWriter"
        case popularContent = "popularContent"
        case childComment = "childComment"
        case childCommentLike = "childCommentLiked"
        case viewitLike = "viewitLiked"
        
        static func from(_ value: String) -> Self? {
            if let result = Self(rawValue: value) {
                return result
            }
            
            WableLogger.log("\(String(describing: Self.self))생성 실패: \(value)", for: .debug)
            return nil
        }
    }

📚 참고자료

  • 프로필은 해당 브랜치에서 QA까지 같이 반영하겠습니다.
  • 로딩뷰의 위치 또한 제 QA로 판단하고 반영하였습니다.

👀 기타 더 이야기해볼 점

  • 이 브랜치에서 빌드를 한 번 돌려봐 주시면 감사하겠습니다.
  • 이미지 'ic_link'와 관련하여, 아마 에러가 날 수 있을 것 같습니다.
  • 빌드 후 에러가 발생한다면, 슬랙으로 이미지를 첨부해 두었으니 해당 이미지로 교체 후 반영해 주시면 감사하겠습니다.

🔗 연결된 이슈

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added support for a new notification type when a recommended link is liked.
    • Enhanced notification handling in the community view, prompting users to enable notifications if not authorized.
    • Improved validation and upload flow for creating new links, including asynchronous URL checks and more precise user feedback.
  • UI Improvements

    • Updated icons and image display modes for a more polished appearance in various views and cells.
    • Adjusted button positions and layout constraints for improved visual alignment and usability.
    • Refined loading screen layout for better centering and balance.
  • Bug Fixes

    • Improved reliability when updating community registration rates and handling notification authorization status.
  • Refactor

    • Streamlined internal logic for URL validation, notification mapping, and reactive event handling for better maintainability and responsiveness.
    • Optimized network session handling for URL previews with custom timeout settings.

@JinUng41 JinUng41 requested a review from youz2me May 18, 2025 17:51
@JinUng41 JinUng41 self-assigned this May 18, 2025
@JinUng41 JinUng41 added 🛠️ fix 기능적 버그나 오류 해결 시 사용 🍻 진웅 술 한잔 가온나~ labels May 18, 2025
@coderabbitai
Copy link

coderabbitai bot commented May 18, 2025

Walkthrough

This update refactors and enhances various UI and logic components related to the Viewit and Community features. It introduces improved notification handling, asynchronous URL validation, layout adjustments, and new notification types. Several user interactions are now reactive, and button positions, image handling, and loading view layouts are updated for better user experience and QA feedback.

Changes

File(s) Change Summary
.../Data/Mapper/NotificationMapper.swift, .../Domain/Enum/TriggerType.swift, .../Presentation/Notification/Activity/Model/ActivityNotification+.swift Changed mapping logic for notification trigger type, added new enum case .viewitLike and static initializer, updated notification message handling for new type.
.../Data/RepositoryImpl/URLPreviewRepositoryImpl.swift Refactored repository from class to struct; creates local URLSession per call; simplified closure logic.
.../Domain/UseCase/Viewit/CreateViewitUseCase.swift, .../Presentation/Viewit/Create/ViewModel/CreateViewitViewModel.swift Refactored use case and ViewModel for asynchronous URL validation and upload, added new outputs and validation logic, split validation and execution.
.../Presentation/Community/View/CommunityViewController.swift, .../Presentation/Community/ViewModel/CommunityViewModel.swift Added notification authorization check relay and output, improved registration rate update logic, streamlined reactive chains.
.../Presentation/Viewit/Create/View/CreateViewitViewController.swift Refactored control flow for next and background tap actions to use reactive streams; added relays and new output subscriptions.
.../Presentation/Viewit/Create/View/Subview/ViewitInputView.swift Changed URL input icon from .add to .icLink.
.../Presentation/Viewit/List/View/Cell/ViewitListCell.swift Updated image view content mode, refined layout constraints, made like button interaction immediate.
.../Presentation/Viewit/List/View/Subview/ViewitListView.swift Changed create button image assignment to configuration-based, adjusted button layout constraints.
.../Presentation/WableComponent/Cell/NotificationCell.swift Changed profile image view content mode to .scaleAspectFill.
.../Presentation/WableComponent/ViewController/LoadingViewController.swift Centered loading animation and message label vertically, updated constraints.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CreateViewitViewController
    participant CreateViewitViewModel
    participant CreateViewitUseCase
    participant URLPreviewRepository

    User->>CreateViewitViewController: Tap "Next"
    CreateViewitViewController->>CreateViewitViewModel: nextRelay.send()
    CreateViewitViewModel->>CreateViewitUseCase: validate(urlString)
    CreateViewitUseCase->>URLPreviewRepository: fetchPreview(url)
    URLPreviewRepository-->>CreateViewitUseCase: URLPreview or Error
    CreateViewitUseCase-->>CreateViewitViewModel: Bool (success/failure)
    CreateViewitViewModel-->>CreateViewitViewController: isPossibleToURLUpload (true/false)
    CreateViewitViewController->>CreateViewitViewModel: User enters description, taps "Upload"
    CreateViewitViewModel->>CreateViewitUseCase: execute(description)
    CreateViewitUseCase-->>CreateViewitViewModel: Bool (success/failure)
    CreateViewitViewModel-->>CreateViewitViewController: successUpload (true/false)
Loading
sequenceDiagram
    participant User
    participant CommunityViewController
    participant CommunityViewModel
    participant UNUserNotificationCenter

    User->>CommunityViewController: Complete registration
    CommunityViewController->>CommunityViewModel: checkNotificationAuthorizationRelay.send()
    CommunityViewModel->>UNUserNotificationCenter: getNotificationSettings()
    UNUserNotificationCenter-->>CommunityViewModel: authorizationStatus
    CommunityViewModel-->>CommunityViewController: isNotificationAuthorized (true/false)
    alt Not authorized
        CommunityViewController->>User: Show alarm setting sheet
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
뷰잇 '+' 버튼 위치 수정, 로딩뷰 위치 수정 (#188)
뷰잇 좋아요 인터랙션 시간 줄이기, 뷰잇 좋아요 알림 추가하기 (#188)
사전신청 후 푸시알림이 허용되있다면, 팝업이 뜨지 않도록 하기 (#188)
커뮤니티 스피너 오류 해결, 링크 업로드 후 뜨는 이미지 수정 (#188)
뷰잇 작성 화면에서 내용이 없는 경우, 그냥 나가기, 뷰잇 링크 작성 시 검증 후 다음 시퀀스로 넘어가기 (#188)

Poem

A bunny hopped through code today,
Moving buttons, links, and views astray.
Likes now bounce with nimble speed,
Notifications pop just when you need.
Centered loaders spin with glee—
QA’s wishes granted, bug-free!
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 13f5d65 and 7a41182.

📒 Files selected for processing (1)
  • Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
Wable-iOS/Presentation/Viewit/Create/ViewModel/CreateViewitViewModel.swift (1)

80-97: ⚠️ Potential issue

Upload is enabled even when URL has not been validated – may trigger the issue above

writeButtonIsEnabled depends only on the description text.
Therefore a user can:

  1. Enter any description (non-empty)
  2. Never press Next / never validate URL
  3. Tap Uploadexecute is called with urlPreview == nil, producing .unknownError.

Couple the enabled state (and the upload trigger) to successful URL validation:

-let writeButtonIsEnabled = descriptionRelay
-    .map { !$0.isEmpty }
+let writeButtonIsEnabled = Publishers.CombineLatest(descriptionRelay, isPossibleToURLUpload)
+    .map { !$0.0.isEmpty && $0.1 }   // both description and valid URL required

Additionally, you may want to gate the execute call itself behind isPossibleToURLUpload to avoid unnecessary network calls.

🧹 Nitpick comments (6)
Wable-iOS/Presentation/Community/View/CommunityViewController.swift (1)

160-160: Consider adding more context to the log message.

While logging community items is useful for debugging, adding more specific context about why they're being logged would make the logs more informative.

-                WableLogger.log("커뮤니티 아이템: \(communityItems)", for: .debug)
+                WableLogger.log("커뮤니티 아이템 수신됨: \(communityItems.count)개", for: .debug)
Wable-iOS/Presentation/Viewit/Create/View/CreateViewitViewController.swift (3)

163-169: Dispatch UI work explicitly on the main queue

sink closures are executed on whichever scheduler the upstream happens to emit.
Although URLSession currently delivers on the main run-loop in your pipeline, this is an implementation detail that can change. Calling UIKit APIs (becomeFirstResponder) from a background thread will crash in production builds.

-output.isPossibleToURLUpload
-    .filter { $0 }
-    .sink { [weak self] _ in
-        self?.viewitInputView.descriptionTextField.becomeFirstResponder()
-    }
+output.isPossibleToURLUpload
+    .filter { $0 }
+    .receive(on: RunLoop.main)          // ✅ guarantee main queue
+    .sink { [weak self] _ in
+        self?.viewitInputView.descriptionTextField.becomeFirstResponder()
+    }

200-203: Dismiss keyboard on background tap for better UX

Currently the background tap only emits an event. Users usually expect the keyboard to fold when they tap outside an input field.

@objc func backgroundViewDidTap() {
-    backgroundTapRelay.send()
+    view.endEditing(true)     // hides keyboard
+    backgroundTapRelay.send()
}

246-254: Prevent retain-cycle inside action closure

confirmAction captures self weakly – great – but WableSheetAction retains its handler until the sheet is deallocated.
If for any reason the sheet lives longer than the controller, the closure still references self (even though weak), so no issue arises.
Nonetheless, it is safer to explicitly nil-out the handler after execution to avoid accidental leaks when the action is reused.

let confirmAction = WableSheetAction(title: "나가기", style: .primary) { [weak self] in
    self?.dismiss(animated: true)
    confirmAction.handler = nil   // break potential retain-cycle
}
Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift (1)

30-34: Return a specific validation error instead of .unknownError

Failing the basic scheme / host checks is a user-input error, not an “unknown” one.
Consider introducing (or re-using) WableError.validationException to give the caller meaningful feedback.

-else {
-    return .fail(.unknownError)
-}
+else {
+    return .fail(.validationException)
+}
Wable-iOS/Presentation/Viewit/Create/ViewModel/CreateViewitViewModel.swift (1)

66-77: Minor: Replace unused closure parameter with _ to satisfy SwiftLint

SwiftLint flagged unused_closure_parameter.
The second element returned by withUnretained is not used, so underscore is fine:

-.flatMap { owner, _ -> AnyPublisher<Bool, Never> in
+.flatMap { owner, _  -> AnyPublisher<Bool, Never> in
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 71-71: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between c3b7df8 and 13f5d65.

📒 Files selected for processing (14)
  • Wable-iOS/Data/Mapper/NotificationMapper.swift (1 hunks)
  • Wable-iOS/Data/RepositoryImpl/URLPreviewRepositoryImpl.swift (1 hunks)
  • Wable-iOS/Domain/Enum/TriggerType.swift (1 hunks)
  • Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift (1 hunks)
  • Wable-iOS/Presentation/Community/View/CommunityViewController.swift (5 hunks)
  • Wable-iOS/Presentation/Community/ViewModel/CommunityViewModel.swift (4 hunks)
  • Wable-iOS/Presentation/Notification/Activity/Model/ActivityNotification+.swift (2 hunks)
  • Wable-iOS/Presentation/Viewit/Create/View/CreateViewitViewController.swift (7 hunks)
  • Wable-iOS/Presentation/Viewit/Create/View/Subview/ViewitInputView.swift (1 hunks)
  • Wable-iOS/Presentation/Viewit/Create/ViewModel/CreateViewitViewModel.swift (4 hunks)
  • Wable-iOS/Presentation/Viewit/List/View/Cell/ViewitListCell.swift (3 hunks)
  • Wable-iOS/Presentation/Viewit/List/View/Subview/ViewitListView.swift (2 hunks)
  • Wable-iOS/Presentation/WableComponent/Cell/NotificationCell.swift (1 hunks)
  • Wable-iOS/Presentation/WableComponent/ViewController/LoadingViewController.swift (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
Wable-iOS/Data/Mapper/NotificationMapper.swift (1)
Wable-iOS/Domain/Enum/TriggerType.swift (1)
  • from (30-37)
Wable-iOS/Domain/Enum/TriggerType.swift (1)
Wable-iOS/Core/Logger/WableLogger.swift (1)
  • log (14-25)
Wable-iOS/Presentation/WableComponent/ViewController/LoadingViewController.swift (1)
Wable-iOS/Presentation/Helper/Extension/ConstraintMaker+.swift (1)
  • adjustedWidthEqualTo (27-30)
Wable-iOS/Presentation/Community/ViewModel/CommunityViewModel.swift (1)
Wable-iOS/Core/Logger/WableLogger.swift (1)
  • log (14-25)
Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift (3)
Wable-iOS/Presentation/Viewit/Create/ViewModel/CreateViewitViewModel.swift (1)
  • validate (114-136)
Wable-iOS/Data/RepositoryImpl/URLPreviewRepositoryImpl.swift (1)
  • fetchPreview (14-54)
Wable-iOS/Data/RepositoryImpl/ViewitRepositoryImpl.swift (2)
  • createViewit (56-77)
  • createViewit (184-194)
🪛 SwiftLint (0.57.0)
Wable-iOS/Presentation/Viewit/Create/ViewModel/CreateViewitViewModel.swift

[Warning] 71-71: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (29)
Wable-iOS/Presentation/WableComponent/ViewController/LoadingViewController.swift (1)

87-87: Improved vertical centering of loading components.

The changes to the layout constraints provide better vertical alignment by positioning elements relative to the view's center rather than using fixed top offsets. This creates a more balanced visual appearance across different screen sizes.

Also applies to: 94-94

Wable-iOS/Presentation/Community/View/CommunityViewController.swift (5)

38-38: Good addition of notification authorization check relay.

The checkNotificationAuthorizationRelay follows the established pattern for handling events with Combine, maintaining consistency with the rest of the code.


152-153: LGTM! Input parameter correctly updated.

The ViewModel's Input is properly extended to include the new notification authorization check relay.


180-185: Good implementation of notification authorization flow.

Filtering for unauthorized notification states to conditionally show the alarm setting sheet is a clean approach that aligns with reactive programming principles.


219-219: Improved user flow with notification check.

The notification authorization check is now appropriately triggered after the registration completion sheet is dismissed, creating a more seamless user experience.


235-235: Good error handling improvement.

Using the logger in the guard's else clause improves error visibility while maintaining clean control flow.

Wable-iOS/Presentation/Community/ViewModel/CommunityViewModel.swift (6)

10-10: Necessary import added.

The UserNotifications framework import is required for the notification authorization functionality.


25-25: Well-structured ViewModel interface.

The Input and Output structs are properly extended to support the new notification authorization check functionality, maintaining the MVVM pattern.

Also applies to: 32-32


106-114: Improved error handling with guard pattern.

The community list update logic now safely handles the case when a team can't be found, using proper guard clauses and logging the issue for debugging.


117-133: Clean refactoring of communityItems mapping.

The simplified mapping of community items improves readability while maintaining the same functionality, with a clear sorting to prioritize registered items.


139-148: Well-implemented notification authorization check.

The isNotificationAuthorized publisher is implemented correctly using Future to handle the asynchronous UNUserNotificationCenter API. This is a clean approach to bridging callback-based APIs with Combine.


153-154: Output struct properly updated.

The Output struct initialization is updated to include the new isNotificationAuthorized property, maintaining consistency with the ViewModel interface.

Wable-iOS/Presentation/Viewit/Create/View/Subview/ViewitInputView.swift (1)

24-24: Icon update improves UI clarity.

The change from what was likely .add to .icLink for the URL input icon better represents the purpose of the field, providing users with a clearer visual cue about the expected input type.

Wable-iOS/Presentation/WableComponent/Cell/NotificationCell.swift (1)

20-20: Improved profile image display.

Changing from .scaleAspectFit to .scaleAspectFill ensures profile images fill the circular frame completely, which is the standard approach for profile pictures in modern UIs. This works well with the existing clipsToBounds = true on the next line to maintain the circular shape.

Wable-iOS/Data/Mapper/NotificationMapper.swift (1)

35-35: Enhanced error handling for notification type mapping.

Using the from(_:) method instead of direct raw value initialization improves traceability by logging debug messages when conversion fails. This matches the PR objective of enhancing enum initialization and will help identify issues with unexpected notification types.

Wable-iOS/Presentation/Notification/Activity/Model/ActivityNotification+.swift (2)

13-13: Better fallback message for unknown notification types.

Replacing an empty string with "알 수 없는 메세지" (Unknown message) provides clearer feedback to users when a notification type is nil, improving the user experience.


41-42: Support added for new viewitLike notification type.

The implementation for the new .viewitLike case follows the established pattern of other notification messages, maintaining consistency in the UI while extending functionality.

Wable-iOS/Domain/Enum/TriggerType.swift (2)

28-28: New notification type added for Viewit likes

The viewitLike case is appropriately added to handle notifications when a user likes another user's viewit content.


30-37: Good addition of a safer enum initializer with error logging

This from(_:) static method provides a safer alternative to raw value initialization with error tracking. It attempts to create an enum instance and logs a debug message if it fails, which improves traceability for debugging notification-related issues.

// Example usage that benefits from this approach:
-if let notificationType = ActivityNotification(rawValue: someString) {
+if let notificationType = ActivityNotification.from(someString) {
    // Success case
} else {
    // Failure case - with the new implementation, a debug log is also generated
}
Wable-iOS/Presentation/Viewit/List/View/Subview/ViewitListView.swift (2)

27-29: Modern button configuration approach

The change to use UIButton(configuration: .plain()) is a good modernization that aligns with current iOS best practices.


93-95: Improved button positioning

The adjustment to the trailing and bottom constraints provides better spacing and alignment for the create button.

Wable-iOS/Presentation/Viewit/List/View/Cell/ViewitListCell.swift (5)

26-27: Improved profile image display

Changing to .scaleAspectFill ensures the profile image properly fills the circular view without distortion, which is the standard approach for profile pictures.


226-227: Better vertical spacing for title label

Removing the negative offset improves the spacing between the title and site name labels.


231-232: Proper constraint relationship between site name and like button

Using lessThanOrEqualTo with the like button ensures the site name label won't overlap with the like button when content sizes vary.


236-237: Adjusted bottom padding for like button

Reducing the bottom offset from -8 to -4 provides better spacing at the bottom of the card.


314-318: Excellent UI responsiveness improvement for like button

This change provides immediate visual feedback when a user taps the like button by updating the UI state before the network call completes. This significantly improves perceived responsiveness.

Wable-iOS/Data/RepositoryImpl/URLPreviewRepositoryImpl.swift (3)

13-13: Simplified to a struct implementation

Converting from a class to a struct is appropriate for this repository as it has no mutable state.


15-20: Improved session configuration with appropriate timeouts

Creating an ephemeral session with custom timeouts for each request is a good practice that:

  1. Prevents caching issues between requests
  2. Sets reasonable timeouts (5s for request, 10s for resource) to avoid long waiting times
  3. Improves the user experience by failing faster on slow connections

25-25: Type-safe error handling in tryMap

The explicit return type in the tryMap closure improves type safety and code clarity.

Comment on lines 21 to 58
private var urlPreview: URLPreview?

func execute(urlString: String, description: String) -> AnyPublisher<Void, WableError> {
func validate(_ urlString: String) -> AnyPublisher<Bool, WableError> {
let updatedURLString = checkURLScheme(urlString)

guard let url = URL(string: updatedURLString) else {
return .fail(.unknownError)
}

guard let scheme = url.scheme, !scheme.isEmpty,
let host = url.host, !host.isEmpty
else {
return .fail(.unknownError)
}

return urlPreviewRepository.fetchPreview(url: url)
.flatMap { [weak self] preview -> AnyPublisher<Void, WableError> in
guard let self else {
return .fail(.unknownError)
}

return viewitRepository.createViewit(
thumbnailImageURLString: preview.imageURLString,
urlString: updatedURLString,
title: preview.title,
text: description,
siteName: preview.siteName
)
.eraseToAnyPublisher()
}
.handleEvents(receiveOutput: { [weak self] preview in
self?.urlPreview = preview
})
.map { _ in true }
.eraseToAnyPublisher()
}

func execute(description: String) -> AnyPublisher<Bool, WableError> {
guard let urlPreview else {
return .fail(.unknownError)
}

return viewitRepository.createViewit(
thumbnailImageURLString: urlPreview.imageURLString,
urlString: urlPreview.urlString,
title: urlPreview.title,
text: description,
siteName: urlPreview.siteName
)
.map { _ in true }
.eraseToAnyPublisher()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

execute can run with a stale or nil urlPreview – causes wrong upload or crash

urlPreview is cached as mutable state:

  1. validate(_: ) sets it only on success.
  2. If a user validates URL A (success) → urlPreview = previewA,
    then validates URL B (failure) → urlPreview is not reset.
  3. The user can still tap Upload (button is enabled independently of validation) and the post will be created with previewA, while the UI shows URL B – clear data inconsistency.

It can also be nil when the user skips validation altogether, leading to the .unknownError branch.

Recommended fix:

  • Reset the cache as soon as a new validation starts.
  • Disable Upload until a successful validation happened (see ViewModel comment).
  • Pass the URLPreview through the publisher chain rather than storing mutable state.
private var urlPreview: URLPreview?

func validate(_ urlString: String) -> AnyPublisher<Bool, WableError> {
-    let updatedURLString = checkURLScheme(urlString)
+    // Invalidate previous cache first
+    urlPreview = nil
+    let updatedURLString = checkURLScheme(urlString)
    ...
}

func execute(description: String) -> AnyPublisher<Bool, WableError> {
-    guard let urlPreview else {
+    guard let urlPreview else {        // still keep the guard for safety
        return .fail(.unknownError)
    }

Long-term: make execute accept the URLPreview instance returned from validate to remove hidden coupling.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var urlPreview: URLPreview?
func execute(urlString: String, description: String) -> AnyPublisher<Void, WableError> {
func validate(_ urlString: String) -> AnyPublisher<Bool, WableError> {
let updatedURLString = checkURLScheme(urlString)
guard let url = URL(string: updatedURLString) else {
return .fail(.unknownError)
}
guard let scheme = url.scheme, !scheme.isEmpty,
let host = url.host, !host.isEmpty
else {
return .fail(.unknownError)
}
return urlPreviewRepository.fetchPreview(url: url)
.flatMap { [weak self] preview -> AnyPublisher<Void, WableError> in
guard let self else {
return .fail(.unknownError)
}
return viewitRepository.createViewit(
thumbnailImageURLString: preview.imageURLString,
urlString: updatedURLString,
title: preview.title,
text: description,
siteName: preview.siteName
)
.eraseToAnyPublisher()
}
.handleEvents(receiveOutput: { [weak self] preview in
self?.urlPreview = preview
})
.map { _ in true }
.eraseToAnyPublisher()
}
func execute(description: String) -> AnyPublisher<Bool, WableError> {
guard let urlPreview else {
return .fail(.unknownError)
}
return viewitRepository.createViewit(
thumbnailImageURLString: urlPreview.imageURLString,
urlString: urlPreview.urlString,
title: urlPreview.title,
text: description,
siteName: urlPreview.siteName
)
.map { _ in true }
.eraseToAnyPublisher()
}
private var urlPreview: URLPreview?
func validate(_ urlString: String) -> AnyPublisher<Bool, WableError> {
// Invalidate previous cache first
urlPreview = nil
let updatedURLString = checkURLScheme(urlString)
guard let url = URL(string: updatedURLString) else {
return .fail(.unknownError)
}
guard let scheme = url.scheme, !scheme.isEmpty,
let host = url.host, !host.isEmpty
else {
return .fail(.unknownError)
}
return urlPreviewRepository.fetchPreview(url: url)
.handleEvents(receiveOutput: { [weak self] preview in
self?.urlPreview = preview
})
.map { _ in true }
.eraseToAnyPublisher()
}
func execute(description: String) -> AnyPublisher<Bool, WableError> {
guard let urlPreview else { // still keep the guard for safety
return .fail(.unknownError)
}
return viewitRepository.createViewit(
thumbnailImageURLString: urlPreview.imageURLString,
urlString: urlPreview.urlString,
title: urlPreview.title,
text: description,
siteName: urlPreview.siteName
)
.map { _ in true }
.eraseToAnyPublisher()
}
🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift between lines 21
and 58, the execute function uses a cached mutable urlPreview that can be stale
or nil, causing incorrect uploads or crashes. To fix this, reset urlPreview to
nil at the start of validate to clear stale data, avoid storing urlPreview as
mutable state, and instead pass the URLPreview instance through the publisher
chain from validate to execute. Also, ensure the UI disables the Upload button
until validation succeeds to prevent execution with invalid or missing previews.
Ultimately, refactor execute to accept URLPreview as a parameter to eliminate
hidden state dependency.

Copy link
Member

@youz2me youz2me left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 조금만 더 힘내봅시드아

Comment on lines +12 to +19
private static let urlDetector: NSDataDetector? = {
do {
return try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
} catch {
WableLogger.log("NSDataDetector 초기화 오류: \(error.localizedDescription)", for: .error)
return nil
}
}()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

urlDetector = NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
이런 식으로는 선언이 불가능한건가 싶어서요, 이렇게 do-catch를 써서 선언하신 이유가 있을까요 ???

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do-catch를 사용한 것은 최소한의 오류를 출력해서 확인하기 위한 장치정도로 봐주시면 감사하겠습니다.
사실 static 프로퍼티가 아니여도 될 듯은 합니다만, 일단 한 번이라도 생성되면 문제없을 것이라 판단하고 타입 프로퍼티로 구현해보았습니다.

Comment on lines 313 to 319
@objc func likeDidTap() {
let newCount = likeButton.isLiked ? likeButton.likeCount - 1 : likeButton.likeCount + 1

likeButton.configureButton(isLiked: !likeButton.isLiked, likeCount: newCount, postType: .content)

likeDidTapClosure?()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 페이지에서도 반복되는 로직인 것 같기도 하구요... 나중에 기회가 되면 합치도록 해볼게요!

@github-project-automation github-project-automation bot moved this to In Review in Wable-iOS May 19, 2025
@JinUng41 JinUng41 merged commit 1c81d82 into develop May 19, 2025
1 check passed
@github-project-automation github-project-automation bot moved this from In Review to Done in Wable-iOS May 19, 2025
@JinUng41 JinUng41 deleted the fix/#188-qa-viewit-community branch May 19, 2025 06:11
youz2me pushed a commit that referenced this pull request Oct 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍻 진웅 술 한잔 가온나~ 🛠️ fix 기능적 버그나 오류 해결 시 사용

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Fix] QA 반영

3 participants