Nothing Special   »   [go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add expense attachment support #73

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

cp-nirali-s
Copy link
Collaborator
@cp-nirali-s cp-nirali-s commented Nov 21, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new date formatting options: shortDate, monthWithYear, and longDate.
    • Added support for associating images with expenses, including image upload functionality.
    • New UI components for image selection and date handling in the expense management interface.
    • New asset entries for calendar and camera icons to enhance visual elements.
  • Improvements

    • Enhanced error handling and user feedback in various components.
    • Streamlined image handling logic in the expense management workflow.
    • Improved date selection and formatting across the application.
    • Updated the checkCameraPermission method to ensure safer URL handling.
  • Bug Fixes

    • Resolved issues related to expense detail display and user interactions.
    • Corrected the title string in the NavigationBarTopView from "Spilt options" to "Split options".
  • Style

    • Updated UI elements for better visual presentation and user experience.
  • Localization

    • Added multiple new string entries and updated existing ones for improved clarity in user interactions.

cp-amisha-i and others added 14 commits October 8, 2024 10:57
* Temp commit

* Minor changes

* Added add expense cloud function

* Added update expense notification cloud function

* Minor changes

* Added delete expense notification cloud function

* Refactored expense cloud functions

* Added add/update/delete transaction cloud function

* Added delete group/remove member cloud function

* Added update group cloud function

* Added updatedBy in groups/expenses/transactions &update it

* Refactored code

* Managed notification tap

* Refactored code

* Refactored code

* Managed notification with activity collection

* Managed notification not shown when app killed

* Seperated cloud functions file

* Seperated function

* Fixed scroll

* Refactored code

* Refactored code

* Refactored code

* Refactored code

* Refactored code

* Fixed pipeline

* Refactored code

* Temp commit

* Redesign add expense & fixed date sheet issue

* Redesign payment detail, add payment screen & refactored code

* localized strings

* Fix: not update group memberOweAmount & balance after adding expense from group list

* Fix pull to refresh not smooth & added scrollToTop btn

* Implement push notifications for each activity

* Fix code signin

* Added rupees symbol before amount

* Added rupees symbol before amount

* Fix: alert btn color & image picker menu text colour

* Refactored code

* Fix getting toast and alert both in record a payment

* Minor improvement

* Add parallel api call & bottom card amount sign changes in split option&multipayer screen

* Minor improvement

* Minor improvement

* Updated cocoapods version

* Fix ui

* Update podfile.lock

* Changed method name

---------

Co-authored-by: Amisha <amisha.i@canopas.com>
Copy link
coderabbitai bot commented Nov 21, 2024

Walkthrough

This pull request introduces several enhancements across multiple files, primarily focusing on date handling and expense management. New computed properties for date formatting are added to the Date extension, while the Double extension's currency formatting method is refactored into a property. The Expense struct is updated to include an optional image URL, and the ExpenseRepository class gains new methods for managing images associated with expenses. Additionally, UI components are refined to improve user interaction, particularly for date and image handling.

Changes

File Path Change Summary
Data/Data/Extension/Date+Extension.swift Added computed properties: shortDate, monthWithYear, and longDate to format dates.
Data/Data/Extension/Double+Extension.swift Refactored formattedCurrency from a method to a computed property; updated formattedCurrencyWithSign logic. Removed the old method that accepted a parameter.
Data/Data/Helper/Firebase/StorageManager.swift Added new enum case expense to ImageStoreType and updated pathName to include a path for this case.
Data/Data/Model/Expense.swift Added optional property imageUrl to Expense struct; updated initializer and CodingKeys to handle the new property.
Data/Data/Repository/ExpenseRepository.swift Updated addExpense method to accept an optional imageData parameter; added updateExpenseWithImage, uploadImage, and hasExpenseChanged methods for enhanced image handling.
Data/Data/Repository/GroupRepository.swift Introduced groupMembersQueue for thread-safe access to groupMembers; updated methods to use this queue for reading and writing.
Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CalendarIcon.imageset/Contents.json Added new JSON configuration for CalendarIcon imageset with two image entries and metadata.
Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CameraIcon.imageset/Contents.json Introduced new JSON configuration for CameraIcon imageset with two image entries and metadata.
Splito/UI/Home/ActivityLog/ActivityLogViewModel.swift Updated filterActivityLogs to group logs by monthWithYear instead of a formatted date string; adjusted error handling in fetchMoreActivityLogs.
Splito/UI/Home/Expense/AddExpenseView.swift Enhanced UI with Kingfisher for image loading; introduced BottomCardView for date and image selection; updated ExpenseInfoView and DatePickerRow to streamline input.
Splito/UI/Home/Expense/AddExpenseViewModel.swift Added properties for image handling (expenseImageUrl, expenseImage, etc.); introduced methods for managing image selection and permissions; integrated image handling into expense addition and update processes.
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift Added conditional rendering for ExpenseImageView based on imageUrl; updated ExpenseHeaderView for improved styling.
Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift Replaced DatePickerRow with DatePickerView in PaymentDateRow; updated TextField for amount input to use a decimal pad and format the amount correctly.
Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift Removed dateFormatter and replaced its usage with monthWithYear for date formatting; adjusted error handling in fetchUserData.
Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift Removed dateFormatter and updated methods to use monthWithYear; improved error handling and data fetching logic.
Data/Data/Store/ExpenseStore.swift Updated expenseReference from private to public; modified addExpense to accept a DocumentReference instead of a groupId and Expense.
Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift Corrected title string in NavigationBarTopView from "Spilt options" to "Split options".
Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift Improved error handling in checkCameraPermission method to ensure settings URL is valid before opening.

Possibly related PRs

  • Fix tab item selection #62: The changes in this PR do not relate to the Date extension modifications in the main PR, as they focus on tab item selection rather than date formatting or extensions.

🐰 "In the land of code where the rabbits play,
New date formats brighten the day!
With images now, expenses can shine,
In the world of Swift, everything's fine!
Let's hop with joy, for changes are here,
Enhancing our app, bringing us cheer!" 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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: 13

🧹 Outside diff range and nitpick comments (20)
Data/Data/Extension/Double+Extension.swift (1)

Line range hint 1-32: Consider adding currency formatting tests.

Given that this extension is critical for expense management and the new attachment feature, it's important to ensure currency formatting remains consistent across different locales and edge cases.

Would you like me to help create comprehensive unit tests covering:

  • Different locales and currency formats
  • Positive and negative values
  • Zero and large numbers
  • Edge cases with attachments
Data/Data/Helper/Firebase/StorageManager.swift (1)

Line range hint 31-45: Consider adding image validation and optimization.

The current implementation could benefit from additional safeguards:

  1. Image size validation and limits
  2. Support for different image formats beyond JPG
  3. Image optimization before upload
  4. Cleanup mechanism for orphaned images

Example improvements for the uploadImage method:

 public func uploadImage(for storeType: ImageStoreType, id: String, imageData: Data) async throws -> String? {
+    guard imageData.count <= 5 * 1024 * 1024 else { // 5MB limit
+        throw StorageError.imageTooLarge
+    }
+    
+    // Detect image type and set appropriate content type
+    let imageType = try detectImageType(from: imageData)
+    let metadata = StorageMetadata()
+    metadata.contentType = imageType.mimeType
+    
+    // Optimize image if needed
+    let optimizedData = try await optimizeImageIfNeeded(imageData, maxSize: 1024)
     let storageRef = storage.reference(withPath: "/\(storeType.pathName)/\(id)")
-    let metadata = StorageMetadata()
-    metadata.contentType = "image/jpg"
Data/Data/Model/Expense.swift (2)

20-20: Add documentation for the new imageUrl property

Consider adding a documentation comment explaining:

  • Expected image URL format
  • Any size/type limitations
  • Whether it supports local or remote URLs

Example:

/// URL to the expense's attached image.
/// Supports remote URLs to cloud storage locations.
/// - Note: Images should follow the app's size and format guidelines
public var imageUrl: String?

Missing test coverage for Expense Codable implementation

The codebase lacks test coverage for the Expense model's Codable implementation. No tests were found that verify:

  • JSON encoding/decoding of Expense model
  • Handling of optional imageUrl property
  • Custom CodingKeys implementation

Action items:

  • Add unit tests in Data/DataTests/DataTests.swift to verify Codable conformance
  • Include test cases for both presence and absence of imageUrl
  • Verify correct mapping of snake_case keys to camelCase properties
🔗 Analysis chain

Line range hint 20-54: Verify Codable implementation testing

The changes affect the Codable implementation. Please ensure test coverage for:

  • Encoding/decoding with imageUrl present
  • Encoding/decoding with imageUrl absent

Let me search for test files and Codable implementation tests specifically.


Let me check the content of the test files we found to look for any Expense model tests.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for relevant test files
fd -e swift -e test expense

Length of output: 749


Script:

#!/bin/bash
# Search for test files and test implementations
fd -e swift test

# Search for Expense model test cases
rg -l "class.*Test.*Expense" 

# Look for Codable related test implementations
rg -A 5 "func test.*Expense.*[Cc]odable|func test.*[Cc]odable.*Expense"

Length of output: 309


Script:

#!/bin/bash
# Check content of test files for any Expense related tests
cat Data/DataTests/DataTests.swift
cat SplitoTests/SplitoTests.swift

# Search for any Codable conformance tests with different pattern
rg -A 5 "XCTestCase.*Expense|test.*Expense" Data/DataTests/DataTests.swift SplitoTests/SplitoTests.swift

Length of output: 2609

Data/Data/Extension/Date+Extension.swift (1)

19-32: Consider moving date formatting to the Presentation layer

The Data layer currently contains UI-specific date formatting logic. This might violate separation of concerns, as formatting for display typically belongs in the Presentation layer.

Consider:

  1. Moving these formatting properties to a UI/Presentation layer extension
  2. Keeping only data-essential date operations in the Data layer
  3. Creating a dedicated DateFormatter service in the Presentation layer

This would improve maintainability and better align with architectural boundaries.

Splito/UI/Home/ActivityLog/ActivityLogViewModel.swift (1)

Line range hint 65-72: Consider enhancing error handling for attachment-related scenarios

The current error handling in fetchMoreActivityLogs is generic. With the addition of expense attachments, consider providing more specific error feedback for attachment-related issues (e.g., attachment size limits, format issues).

Consider updating the error handling like this:

 } catch {
-    viewState = .initial
-    showToastForError()
+    viewState = .initial
+    if let attachmentError = error as? AttachmentError {
+        showToastFor(attachmentError)
+    } else {
+        showToastForError()
+    }
 }
Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (2)

Line range hint 168-182: Add amount validation and handle edge cases.

The current implementation could benefit from additional validation:

  1. Maximum amount validation to prevent overflow
  2. Handling of negative numbers
  3. Decimal place limitations

Consider adding these validations to the formatAmount function:

 private func formatAmount(newValue: String) {
     let numericInput = newValue.replacingOccurrences(of: "₹", with: "").trimmingCharacters(in: .whitespaces)
     if let value = Double(numericInput) {
+        // Validate maximum amount
+        guard value <= 999999999 else { return }
+        // Ensure positive amount
+        guard value >= 0 else { return }
+        // Limit decimal places to 2
+        let formatted = String(format: "%.2f", value)
+        guard let finalValue = Double(formatted) else { return }
-        amount = value
+        amount = finalValue
     } else {
         amount = 0
     }
     amountString = numericInput.isEmpty ? "" : "₹ " + numericInput
 }

Line range hint 144-182: Consider localization for currency formatting.

The currency symbol "₹" is hardcoded. Consider using NumberFormatter for proper locale-aware currency formatting.

Here's a suggested approach:

 struct AmountRowView: View {
+    private let currencyFormatter: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .currency
+        formatter.locale = Locale(identifier: "en_IN") // Or use system locale
+        return formatter
+    }()

     // ... existing code ...

     private func formatAmount(newValue: String) {
-        let numericInput = newValue.replacingOccurrences(of: "₹", with: "").trimmingCharacters(in: .whitespaces)
+        let numericInput = newValue.replacingOccurrences(of: currencyFormatter.currencySymbol ?? "₹", with: "").trimmingCharacters(in: .whitespaces)
         if let value = Double(numericInput) {
             amount = value
         } else {
             amount = 0
         }
-        amountString = numericInput.isEmpty ? "" : "₹ " + numericInput
+        amountString = numericInput.isEmpty ? "" : "\(currencyFormatter.currencySymbol ?? "₹") " + numericInput
     }
 }
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (2)

29-33: Consider enhancing image handling for better UX.

The image display implementation looks good, but could benefit from additional user experience improvements.

Consider adding:

  1. Loading state while the image is being fetched
  2. Error handling for failed image loads
  3. Tap gesture to view the image in full screen
 if let imageUrl = viewModel.expense?.imageUrl {
     ExpenseImageView(imageUrl: imageUrl)
         .aspectRatio(16/9, contentMode: .fit)
         .cornerRadius(12)
+        .onTapGesture {
+            // TODO: Show full screen image view
+        }
 }

29-33: Consider implementing image caching and size optimization.

To improve performance and reduce memory usage:

  1. Implement image caching to prevent unnecessary network requests
  2. Consider implementing image size optimization, especially for large images
  3. Add image compression before upload (in the related upload functionality)

This will help:

  • Reduce network bandwidth usage
  • Improve loading times for frequently viewed images
  • Optimize memory usage when displaying multiple expenses with images
Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift (3)

Line range hint 13-15: Consider making EXPENSES_LIMIT configurable.

The hardcoded EXPENSES_LIMIT = 10 could be made more flexible by moving it to a configuration file or making it adjustable based on device capabilities and network conditions.

-    private let EXPENSES_LIMIT = 10
+    private var expensesLimit: Int {
+        // Return different limits based on device/network conditions
+        return preference.isLowPowerMode ? 5 : 10
+    }

Line range hint 232-239: Optimize expense filtering performance.

The filtering logic in updateGroupExpenses performs multiple operations that could be optimized:

  1. Multiple calls to uniqued()
  2. Case-insensitive string comparison in a loop
  3. String to Double conversion attempt for each expense

Consider this optimization:

     private func updateGroupExpenses() {
-        let filteredExpenses = expensesWithUser.uniqued().filter { expense in
-            searchedExpense.isEmpty ||
-            expense.expense.name.lowercased().contains(searchedExpense.lowercased()) ||
-            expense.expense.amount == Double(searchedExpense)
-        }
-        self.groupExpenses = Dictionary(grouping: filteredExpenses.uniqued().sorted { $0.expense.date.dateValue() > $1.expense.date.dateValue() }) { expense in
-            return expense.expense.date.dateValue().monthWithYear
-        }
+        let searchTerm = searchedExpense.lowercased()
+        let searchAmount = Double(searchedExpense)
+        
+        let uniqueExpenses = expensesWithUser.uniqued()
+        let filteredExpenses = searchedExpense.isEmpty ? uniqueExpenses : uniqueExpenses.filter { expense in
+            expense.expense.name.lowercased().contains(searchTerm) ||
+            (searchAmount != nil && expense.expense.amount == searchAmount)
+        }
+        
+        self.groupExpenses = Dictionary(grouping: filteredExpenses.sorted { $0.expense.date.dateValue() > $1.expense.date.dateValue() }) { expense in
+            expense.expense.date.dateValue().monthWithYear
+        }

Line range hint 64-71: Structure notification handling more robustly.

The notification handling could be more maintainable by:

  1. Using a dedicated enum for notification names
  2. Centralizing notification registration
  3. Adding proper cleanup in deinit

Consider creating a dedicated notification handler:

private extension GroupHomeViewModel {
    enum GroupNotification: String {
        case addExpense = "addExpense"
        case updateExpense = "updateExpense"
        case deleteExpense = "deleteExpense"
        case addTransaction = "addTransaction"
        case updateTransaction = "updateTransaction"
        case deleteTransaction = "deleteTransaction"
        case updateGroup = "updateGroup"
        
        var name: Notification.Name {
            return Notification.Name(self.rawValue)
        }
    }
    
    func registerNotifications() {
        GroupNotification.allCases.forEach { notification in
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(handleNotification(_:)),
                name: notification.name,
                object: nil
            )
        }
    }
    
    @objc private func handleNotification(_ notification: Notification) {
        guard let notificationType = GroupNotification(rawValue: notification.name.rawValue) else {
            return
        }
        
        switch notificationType {
            case .addExpense: handleAddExpense(notification: notification)
            case .updateExpense: handleUpdateExpense(notification: notification)
            // ... handle other cases
        }
    }
}
Data/Data/Repository/GroupRepository.swift (2)

Line range hint 213-234: Enhance bulk member fetching implementation

The current implementation has similar thread safety concerns as fetchMemberBy:

  1. Async barrier writes should be awaited
  2. Consider using a Set for more efficient duplicate checking
  3. Add structured error handling for the async operations

Here's a suggested implementation:

 let missingMemberIds = memberIds.filter { memberId in
     let cachedMember = self.groupMembersQueue.sync { self.groupMembers.first { $0.id == memberId } }
     return cachedMember == nil
 }

 if missingMemberIds.isEmpty {
     return groupMembersQueue.sync { self.groupMembers.filter { memberIds.contains($0.id) } }
 }

 try await withThrowingTaskGroup(of: AppUser?.self) { groupTask in
     for memberId in missingMemberIds {
         groupTask.addTask {
             try await self.fetchMemberBy(userId: memberId)
         }
     }

     for try await member in groupTask {
         if let member {
             members.append(member)
-            groupMembersQueue.async(flags: .barrier) {
-                if !self.groupMembers.contains(where: { $0.id == member.id }) {
-                    self.groupMembers.append(member)
-                }
-            }
+            // Use async/await with barrier to ensure write completion
+            try await groupMembersQueue.async(flags: .barrier) {
+                // Use a more efficient way to check duplicates
+                let memberIds = Set(self.groupMembers.map { $0.id })
+                if !memberIds.contains(member.id) {
+                    self.groupMembers.append(member)
+                }
+            }
         }
     }
 }

Line range hint 11-23: Consider adding documentation for concurrent access patterns

The class has a good architecture with proper dependency injection and separation of concerns. However, it would benefit from documentation explaining:

  1. The thread safety approach for groupMembers
  2. When and why barrier operations are used
  3. The caching strategy for group members

This will help maintainers understand the concurrent access patterns.

Data/Data/Repository/ExpenseRepository.swift (2)

68-73: Use Equatable conformance for expense comparison

The hasExpenseChanged method manually compares each property of the Expense object. This can be error-prone and hard to maintain as the Expense structure evolves.

Consider making Expense conform to the Equatable protocol and using a simple comparison:

private func hasExpenseChanged(_ expense: Expense, oldExpense: Expense) -> Bool {
    return oldExpense != expense
}

This approach simplifies the code and reduces the risk of missing property comparisons.


Line range hint 75-80: Handle potential nil group.id before updating expense

In the updateExpense method, you guard against a nil groupId but don't provide feedback or throw an error if it's nil. This could lead to silent failures.

Consider throwing an error or handling the nil case explicitly to ensure that the calling code is aware of the failure.

 public func updateExpense(group: Groups, expense: Expense, oldExpense: Expense, type: ActivityType) async throws {
     guard let groupId = group.id else {
+        throw NSError(domain: "ExpenseRepository", code: 0, userInfo: [NSLocalizedDescriptionKey: "Group ID is nil"])
+    }
     try await store.updateExpense(groupId: groupId, expense: expense)
     try await addActivityLogForExpense(group: group, expense: expense, oldExpense: oldExpense, type: type)
 }
🧰 Tools
🪛 SwiftLint

[Warning] 41-41: Function should have 5 parameters or less: it currently has 6

(function_parameter_count)

Splito/UI/Home/Expense/AddExpenseViewModel.swift (3)

25-25: Confirm the use of UIImage with SwiftUI

You're using UIImage for expenseImage in a SwiftUI context. Consider using SwiftUI's Image type or ensure that UIImage is compatible with your SwiftUI views to maintain consistency and take full advantage of SwiftUI features.


363-364: Refactor image processing to avoid code duplication

The image resizing and compression logic is duplicated in both addExpense and updateExpense methods. Extract this code into a helper method to improve maintainability and reduce repetition.

Extract the image processing into a helper method:

private func prepareImageData() -> Data? {
    guard let resizedImage = expenseImage?.aspectFittedToHeight(200) else { return nil }
    return resizedImage.jpegData(compressionQuality: 0.2)
}

Update your methods to use the helper:

// In addExpense(group:expense:)
- let resizedImage = expenseImage?.aspectFittedToHeight(200)
- let imageData = resizedImage?.jpegData(compressionQuality: 0.2)
+ let imageData = prepareImageData()

// In updateExpense(group:expense:oldExpense:)
- let resizedImage = expenseImage?.aspectFittedToHeight(200)
- let imageData = resizedImage?.jpegData(compressionQuality: 0.2)
+ let imageData = prepareImageData()

Also applies to: 411-412


363-364: Consider adjusting the image compression quality

The JPEG compression quality is set to 0.2, which may result in low image quality. Consider increasing the compression quality to 0.5 or 0.7 to achieve a better balance between image quality and file size.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8c7dbcf and ac303a0.

⛔ Files ignored due to path filters (4)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CalendarIcon.imageset/date.icon (1).svg is excluded by !**/*.svg
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CalendarIcon.imageset/date.icon.svg is excluded by !**/*.svg
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CameraIcon.imageset/camera.icon (1).png is excluded by !**/*.png
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CameraIcon.imageset/camera.icon (2).png is excluded by !**/*.png
📒 Files selected for processing (15)
  • Data/Data/Extension/Date+Extension.swift (1 hunks)
  • Data/Data/Extension/Double+Extension.swift (1 hunks)
  • Data/Data/Helper/Firebase/StorageManager.swift (1 hunks)
  • Data/Data/Model/Expense.swift (2 hunks)
  • Data/Data/Repository/ExpenseRepository.swift (2 hunks)
  • Data/Data/Repository/GroupRepository.swift (4 hunks)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CalendarIcon.imageset/Contents.json (1 hunks)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CameraIcon.imageset/Contents.json (1 hunks)
  • Splito/UI/Home/ActivityLog/ActivityLogViewModel.swift (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (7 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (7 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (2 hunks)
  • Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (1 hunks)
  • Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift (1 hunks)
  • Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CalendarIcon.imageset/Contents.json
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/CameraIcon.imageset/Contents.json
🧰 Additional context used
🪛 SwiftLint
Data/Data/Repository/ExpenseRepository.swift

[Warning] 41-41: Function should have 5 parameters or less: it currently has 6

(function_parameter_count)

🔇 Additional comments (13)
Data/Data/Helper/Firebase/StorageManager.swift (2)

15-15: LGTM! The new enum case follows existing conventions.

The addition of the expense case aligns well with the PR objective and maintains consistency with existing cases.


23-24: Verify storage path compatibility.

While the path naming follows the existing convention, ensure that "expense_images" is compatible with Firebase Storage path requirements and organizational policies.

✅ Verification successful

Let me gather more information about the storage path patterns and related code.


Based on the gathered information, I can now provide the final response:

Storage path follows consistent naming pattern and is safe to use

The expense_images path follows the established pattern used throughout the StorageManager, consistent with other cases:

  • user_images
  • group_images
  • expense_images

The path uses simple alphanumeric characters with underscores, which is compatible with Firebase Storage path requirements. The consistent naming pattern across all cases indicates an established convention within the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if similar path patterns are used consistently across the codebase
# and if there are any existing guidelines for storage paths

# Search for storage path definitions
rg -g '*.swift' 'pathName.*images"'

# Look for any storage path related documentation
fd -g 'README*' -X grep -l 'storage' || fd -g '*.md' -X grep -l 'storage'

Length of output: 124


Script:

#!/bin/bash
# Search for the entire enum and switch case implementation to understand the context
ast-grep --pattern 'enum StorageFolder {
  $$$
}'

# Search for the complete pathName implementation
ast-grep --pattern 'var pathName: String {
  $$$
}'

# Check the content of the storage documentation
cat docs/index.md

Length of output: 13918

Data/Data/Model/Expense.swift (2)

27-35: LGTM! Well-structured initializer update

The initializer changes are well-implemented:

  • Maintains backward compatibility with optional parameter
  • Logical parameter ordering
  • Consistent with existing style

50-50: LGTM! Consistent coding key convention

The new coding key follows the established snake_case convention for JSON serialization.

Splito/UI/Home/ActivityLog/ActivityLogViewModel.swift (2)

100-100: Verify the monthWithYear property implementation

The code now uses a new monthWithYear computed property from the Date extension. While this change simplifies the date grouping logic, we should ensure the property is properly implemented.

Let's verify the implementation of this property:

✅ Verification successful

The monthWithYear property is correctly implemented and used consistently

The monthWithYear property is properly implemented in Date+Extension.swift using a standard DateFormatter with "MMMM yyyy" format (e.g., "November 2024"). The property is used consistently across the codebase for date grouping in activity logs, expenses, and transactions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the monthWithYear property definition
ast-grep --pattern 'extension Date {
  $$$
  var monthWithYear: $_ {
    $$$
  }
  $$$
}'

# Backup: Search using ripgrep if the property is defined differently
rg -p "monthWithYear" --type swift

Length of output: 656


Script:

#!/bin/bash
# Let's check the implementation of monthWithYear in Date+Extension.swift
rg -A 5 "var monthWithYear" Data/Data/Extension/Date+Extension.swift

Length of output: 248


Line range hint 82-97: Consider adding support for attachment-related activities

Since this PR adds expense attachment support, the handleActivityItemTap method might need to handle new activity types related to expense attachments (e.g., attachment added, removed, or updated).

Let's check if there are any new activity types we need to handle:

Consider adding cases for attachment-related activities if they're defined in the ActivityType enum.

Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (1)

105-106: LGTM! Clean styling implementation.

Good optimization by simplifying the view hierarchy and applying styling directly instead of using an overlay.

Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift (1)

239-239: Verify date formatting consistency across expense grouping.

The change to use monthWithYear property aligns with the standardized date formatting approach. However, let's verify that all expense date grouping operations use this consistent format.

✅ Verification successful

Let me check the implementation of monthWithYear to ensure it's properly standardized.


Date formatting is consistently implemented across expense grouping

The verification shows that:

  • monthWithYear is properly implemented as a Date extension with a standardized format "MMMM yyyy"
  • It's consistently used across all relevant views:
    • GroupHomeViewModel (expense grouping)
    • GroupTransactionListViewModel (transactions)
    • ActivityLogViewModel (activity logs)
  • Only one legacy date formatting remains in ActivityLogView which uses a different context
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining direct date formatting in expense grouping logic
# across the codebase to ensure consistency.

# Search for date formatting patterns in Swift files
rg -t swift 'dateFormatter.*month|Date.*format.*month' 

# Search for monthWithYear usage to verify consistent adoption
rg -t swift 'monthWithYear'

Length of output: 812


Script:

#!/bin/bash
# Check the implementation of monthWithYear
rg -A 5 -B 2 'var monthWithYear: String'

Length of output: 570

Data/Data/Repository/GroupRepository.swift (1)

23-23: Well-implemented concurrent queue for thread safety!

Good practice using a concurrent queue with a descriptive label for managing access to shared state.

Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift (2)

211-211: LGTM: Date formatting refactor looks clean.

The switch to using the monthWithYear computed property from Date extension makes the code more maintainable and consistent.

Also applies to: 218-218


Line range hint 1-324: Implementation for expense attachment support appears to be missing.

While the PR's objective is to add expense attachment support, this view model doesn't contain any changes related to handling attachments or images for expenses. Consider adding:

  • Properties to track attachment state
  • Methods to handle attachment selection/preview
  • Logic to upload/download attachments
  • UI state updates for attachment loading

Let's verify if attachment-related code exists in other files:

Consider implementing the following components for a robust attachment system:

  1. Attachment handling methods in the view model
  2. Image upload/download service integration
  3. Progress tracking for uploads
  4. Error handling for failed uploads/downloads
  5. Cache management for downloaded images

Would you like me to help design these components?

Data/Data/Repository/ExpenseRepository.swift (1)

58-59: Verify that image changes trigger expense updates correctly

The hasExpenseChanged method checks whether the expense has changed before updating. Ensure that changes to the image (addition, deletion, or modification) are properly detected so that the expense is updated in the store when image changes occur.

Confirm that hasExpenseChanged accounts for image-related changes and that the expense is updated accordingly.

Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

11-12: Added imports are appropriate

The imports of AVFoundation and SwiftUI are necessary for the new functionalities related to camera access and SwiftUI views.

Data/Data/Extension/Double+Extension.swift Show resolved Hide resolved
Data/Data/Extension/Double+Extension.swift Show resolved Hide resolved
Data/Data/Model/Expense.swift Show resolved Hide resolved
Data/Data/Extension/Date+Extension.swift Show resolved Hide resolved
Data/Data/Repository/ExpenseRepository.swift Show resolved Hide resolved
Data/Data/Repository/ExpenseRepository.swift Outdated Show resolved Hide resolved
Data/Data/Repository/ExpenseRepository.swift Outdated Show resolved Hide resolved
Splito/UI/Home/Expense/AddExpenseView.swift Outdated Show resolved Hide resolved
Splito/UI/Home/Expense/AddExpenseViewModel.swift Outdated Show resolved Hide resolved
@canopas canopas deleted a comment from coderabbitai bot Nov 21, 2024
@canopas canopas deleted a comment from coderabbitai bot Nov 21, 2024
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: 4

🧹 Outside diff range and nitpick comments (6)
Data/Data/Store/ExpenseStore.swift (1)

24-26: Consider providing a higher-level API for better usability

The current implementation exposes Firebase-specific types (DocumentReference) in the public API, which could make the code harder to test and maintain. Consider providing a higher-level API that abstracts these details.

+func addExpense(groupId: String, expense: Expense) async throws {
+    let documentRef = expenseReference(groupId: groupId).document()
+    try await addExpense(documentRef: documentRef, expense: expense)
+    return documentRef.documentID
+}

 func addExpense(documentRef: DocumentReference, expense: Expense) async throws {
     try documentRef.setData(from: expense)
 }
Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift (1)

53-55: LGTM! Consider adding user feedback for edge cases.

The nil check for settings URL is a good safety improvement. However, in the rare case where the settings URL is invalid, the user receives no feedback about why they can't proceed.

Consider adding an else clause to handle the edge case:

 if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
     UIApplication.shared.open(settingsURL)
+} else {
+    self?.showToastFor(toast: ToastPrompt(
+        type: .error,
+        title: "Error",
+        message: "Unable to open Settings. Please enable camera access manually."
+    ))
 }
Data/Data/Repository/ExpenseRepository.swift (1)

45-64: Consider enhancing error handling for image operations

While the implementation handles different image update scenarios well, consider adding more specific error handling for image operations.

 public func updateExpenseWithImage(imageData: Data?, newImageUrl: String?, group: Groups, expense: (new: Expense, old: Expense), type: ActivityType) async throws -> Expense {
     var updatedExpense = expense.new
+    
+    enum ImageUpdateError: Error {
+        case imageUploadFailed
+        case imageDeletionFailed
+    }

     if let imageData {
         let uploadedImageUrl = try await uploadImage(imageData: imageData, expense: updatedExpense)
+        guard !uploadedImageUrl.isEmpty else {
+            throw ImageUpdateError.imageUploadFailed
+        }
         updatedExpense.imageUrl = uploadedImageUrl
     } else if let currentUrl = updatedExpense.imageUrl, newImageUrl == nil {
         try await storageManager.deleteImage(imageUrl: currentUrl)
         updatedExpense.imageUrl = nil
     } else if let newImageUrl = newImageUrl {
+        guard !newImageUrl.isEmpty else {
+            throw ImageUpdateError.imageUploadFailed
+        }
         updatedExpense.imageUrl = newImageUrl
     }
Splito/UI/Home/Expense/AddExpenseView.swift (2)

118-119: Consider standardizing the layout spacing

The spacing between ExpenseDetailRow components varies. Consider using consistent spacing values for better visual hierarchy:

  • Between group selection and description
  • Between amount and payment details
 VStack(spacing: 16) {
-    ExpenseDetailRow(name: $viewModel.expenseName, focusedField: focusedField, subtitle: "With you and:",
+    VStack(spacing: 24) {  // Increased spacing for major sections
+        ExpenseDetailRow(name: $viewModel.expenseName, focusedField: focusedField, subtitle: "With you and:",
             inputValue: viewModel.selectedGroup?.name ?? "Select group",
             showButton: true, onTap: viewModel.handleGroupBtnAction)

Also applies to: 122-123, 132-136


237-238: Consider more flexible date range handling

The maximum date calculation is hardcoded to the current year. Consider:

  1. Making the date range configurable through parameters
  2. Using a computed property for dynamic updates
-    private let maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date()) ?? Date()
+    private var maximumDate: Date {
+        Calendar.current.startOfDay(for: Date())
+    }
Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

365-366: Consider increasing the image compression quality for better image clarity

Compressing the image with a compressionQuality of 0.2 may result in noticeably low image quality, affecting the user experience. To achieve a better balance between image size and quality, consider using a higher compression quality, such as 0.6 or 0.7.

Also applies to: 413-414

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ac303a0 and 46a5e39.

📒 Files selected for processing (7)
  • Data/Data/Repository/ExpenseRepository.swift (2 hunks)
  • Data/Data/Repository/GroupRepository.swift (3 hunks)
  • Data/Data/Store/ExpenseStore.swift (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (7 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (7 hunks)
  • Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift (1 hunks)
  • Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Data/Data/Repository/GroupRepository.swift
🔇 Additional comments (9)
Data/Data/Store/ExpenseStore.swift (1)

17-22: Verify the necessity of internal access for expenseReference

The method's access level has been changed from private to internal. While this might be needed for image handling coordination, exposing Firestore collection references could lead to inconsistent data access patterns.

✅ Verification successful

Internal access for expenseReference is appropriate

The internal access level is justified as it follows a clean architecture pattern where:

  • ExpenseRepository acts as a public-facing repository layer
  • ExpenseStore serves as an internal implementation detail
  • The reference is only used within the data layer for proper encapsulation
  • Usage is controlled and consistent through dependency injection (via AppAssembly)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if this reference is used outside this file
rg -p "expenseReference" --type swift --glob '!**/ExpenseStore.swift'

Length of output: 202


Script:

#!/bin/bash
# Let's check the ExpenseRepository usage to understand the context better
rg -p "class ExpenseRepository" -A 10 --type swift

# Also check if there are any tests or other legitimate uses
rg -p "ExpenseRepository" --type swift

Length of output: 1641

Data/Data/Repository/ExpenseRepository.swift (5)

14-14: LGTM: StorageManager dependency properly injected

The StorageManager dependency is correctly injected following the existing pattern and will be used for handling image uploads and deletions.


17-31: LGTM: Improved expense creation flow with proper image handling

The implementation correctly:

  • Generates the document ID first
  • Handles image upload before storing the expense
  • Updates the expense with the image URL if provided

66-69: Previous comment about nil expense.id handling still applies

The current implementation returns an empty string when expense.id is nil, which could lead to silent failures.


71-77: LGTM: Comprehensive change detection implementation

The method thoroughly compares all relevant fields, including the new imageUrl field, in a clear and maintainable way.


14-14: Verify consistent image handling across the codebase

Let's verify that image-related operations are handled consistently across the codebase.

Also applies to: 17-31, 45-64, 66-69

✅ Verification successful

Image handling is consistently implemented across the codebase

Based on the verification results:

  • All image operations are centralized through the StorageManager class
  • Image handling is consistently used in repositories (ExpenseRepository, GroupRepository, UserRepository)
  • The StorageManager provides a unified interface for image upload/delete operations
  • Image-related operations are properly categorized using ImageStoreType enum
  • All UI components consistently use the same image handling patterns through repository layer
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent image handling patterns

# Look for image-related operations
echo "Checking for image upload/delete operations:"
rg -l "uploadImage|deleteImage|imageUrl" --type swift

# Check for potential image handling in other repositories
echo "\nChecking for other image handling implementations:"
rg -l "class.*Repository.*{" --type swift | xargs rg "image|photo|attachment" -l

# Look for StorageManager usage
echo "\nChecking StorageManager usage patterns:"
rg "StorageManager" --type swift -A 3

Length of output: 12610

Splito/UI/Home/Expense/AddExpenseView.swift (2)

76-83: LGTM: Well-structured image picker implementation

The image picker implementation follows iOS best practices with proper sheet presentation and confirmation dialog handling.


Line range hint 163-191: Verify the necessity of forced tap gesture

The use of onTapGestureForced might interfere with the natural touch handling of buttons and text fields. Consider using regular onTapGesture unless there's a specific reason for forcing the gesture.

Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

227-231: Ensure UI updates occur on the main thread and handle permission denial appropriately

In the checkCameraPermission method:

  • Thread Safety: The closure in AVCaptureDevice.requestAccess may run on a background thread. Wrap the authorized() call inside DispatchQueue.main.async to ensure UI updates occur on the main thread.
  • Handle Permission Denial: If permission is not granted (granted == false), consider informing the user that camera access is required for this feature.

Data/Data/Store/ExpenseStore.swift Show resolved Hide resolved
Splito/UI/Home/Expense/AddExpenseView.swift Outdated Show resolved Hide resolved
Splito/UI/Home/Expense/AddExpenseViewModel.swift Outdated Show resolved Hide resolved
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: 2

🧹 Outside diff range and nitpick comments (8)
Splito/UI/Home/Account/User Profile/UserProfileViewModel.swift (3)

89-91: Good URL safety, but consider these improvements

The URL handling is properly implemented with optional binding. However, consider these enhancements:

  1. Move the alert dismissal before opening settings
  2. Add completion handler to handle URL opening failures
  3. Use [weak self] explicitly for better memory management
-                if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
-                    UIApplication.shared.open(settingsURL)
-                }
-                self?.showAlert = false
+                self?.showAlert = false
+                if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
+                    UIApplication.shared.open(settingsURL, options: [:]) { success in
+                        if !success {
+                            self?.showAlertFor(message: "Could not open Settings")
+                        }
+                    }
+                }

Line range hint 1-400: Consider splitting responsibilities for better maintainability

The UserProfileViewModel handles multiple concerns which could be separated into dedicated services:

  1. Extract authentication logic into an AuthenticationService
  2. Move image handling into an ImageService
  3. Create a dedicated ProfileManager for profile data operations

This separation would:

  • Improve testability
  • Make the code more maintainable
  • Allow reuse of these services across the app

Example structure:

// AuthenticationService.swift
protocol AuthenticationServiceProtocol {
    func reAuthenticate(for loginType: LoginType) async throws -> AuthCredential
}

// ImageService.swift
protocol ImageServiceProtocol {
    func checkCameraPermission() async throws
    func handleImageSelection(source: ImageSource) async throws -> UIImage
}

// ProfileManager.swift
protocol ProfileManagerProtocol {
    func updateProfile(user: User, image: UIImage?) async throws
    func deleteProfile() async throws
}

// Simplified ViewModel
class UserProfileViewModel: BaseViewModel, ObservableObject {
    private let authService: AuthenticationServiceProtocol
    private let imageService: ImageServiceProtocol
    private let profileManager: ProfileManagerProtocol
    
    // Reduced complexity with dependency injection
}

Line range hint 1-400: Improve error handling consistency and structure

The current error handling could be enhanced for better maintainability and consistency:

  1. Use structured error types instead of string comparisons
  2. Centralize error messages
  3. Migrate completion handlers to async/await

Example implementation:

enum UserProfileError: LocalizedError {
    case reauthenticationRequired
    case invalidCredentials
    case permissionDenied
    case networkError(Error)
    
    var errorDescription: String? {
        switch self {
        case .reauthenticationRequired:
            return "Recent authentication is required"
        case .invalidCredentials:
            return "Invalid credentials provided"
        case .permissionDenied:
            return "Camera access is required"
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        }
    }
}

// Example usage:
func deleteUser() async throws {
    guard let user = preference.user else {
        throw UserProfileError.invalidCredentials
    }
    
    do {
        try await userRepository.deleteUser(id: user.id)
        preference.clearPreferenceSession()
        goToOnboardScreen()
    } catch {
        if error.localizedDescription.contains(REQUIRE_AGAIN_LOGIN_TEXT) {
            throw UserProfileError.reauthenticationRequired
        }
        throw UserProfileError.networkError(error)
    }
}
Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

Line range hint 364-421: Improve image processing and validation

The image handling implementation could be enhanced:

  1. Magic numbers should be constants
  2. Large images could cause memory issues
  3. Missing image size validation

Apply these improvements:

+private enum ImageConstants {
+    static let maxHeight: CGFloat = 200
+    static let compressionQuality: CGFloat = 0.2
+    static let maxImageSizeInBytes = 5 * 1024 * 1024 // 5MB
+}

+private func processImage(_ image: UIImage?) -> Data? {
+    guard let image = image else { return nil }
+    let resizedImage = image.aspectFittedToHeight(ImageConstants.maxHeight)
+    guard let imageData = resizedImage?.jpegData(compressionQuality: ImageConstants.compressionQuality),
+          imageData.count <= ImageConstants.maxImageSizeInBytes else {
+        showAlertFor(alert: .init(
+            title: "Image Too Large",
+            message: "Please select an image smaller than 5MB",
+            positiveBtnTitle: "OK"
+        ))
+        return nil
+    }
+    return imageData
+}

 private func addExpense(group: Groups, expense: Expense) async -> Bool {
     guard let groupId = group.id else { return false }

-    let resizedImage = expenseImage?.aspectFittedToHeight(200)
-    let imageData = resizedImage?.jpegData(compressionQuality: 0.2)
+    let imageData = processImage(expenseImage)

     do {
         showLoader = true

Apply similar changes to updateExpense method.

Splito/UI/Home/Expense/AddExpenseView.swift (4)

76-80: Consider adding error handling for image picker failures

The image picker implementation should handle potential failures when accessing the camera or photo library.

 .sheet(isPresented: $viewModel.showImagePicker) {
     ImagePickerView(cropOption: .square,
                     sourceType: !viewModel.sourceTypeIsCamera ? .photoLibrary : .camera,
-                    image: $viewModel.expenseImage, isPresented: $viewModel.showImagePicker)
+                    image: $viewModel.expenseImage,
+                    isPresented: $viewModel.showImagePicker,
+                    onError: { error in
+                        viewModel.toast = .init(style: .error, message: error.localizedDescription)
+                    })
 }

132-136: Consider using enum for input types to improve type safety

The ExpenseDetailRow is used for multiple purposes with different input types. Using an enum would make the intent clearer and prevent potential misuse.

+ enum ExpenseDetailRowType {
+     case text(Binding<String>)
+     case button(String, () -> Void)
+ }

  struct ExpenseDetailRow: View {
-     @Binding var name: String
+     let type: ExpenseDetailRowType
      // ... rest of the implementation
  }

250-257: Add accessibility labels for better VoiceOver support

The date display should have appropriate accessibility labels to improve the experience for VoiceOver users.

 if !isForAddExpense {
     Text(date.longDate)
         .font(.subTitle2())
         .foregroundStyle(primaryText)
         .frame(maxWidth: .infinity, alignment: .leading)
+        .accessibilityLabel("Selected date: \(date.longDate)")
 } else {
     DateDisplayView(date: $date)
 }

326-343: Add haptic feedback for better user interaction

Adding haptic feedback when tapping the image picker button would improve the user experience.

 Button(action: {
+    UIImpactFeedbackGenerator(style: .light).impactOccurred()
     handleImageBtnTap()
 }) {
     // ... rest of the implementation
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 46a5e39 and 4c4655d.

📒 Files selected for processing (4)
  • Splito/UI/Home/Account/User Profile/UserProfileViewModel.swift (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (7 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (7 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift
🧰 Additional context used
🪛 SwiftLint (0.57.0)
Splito/UI/Home/Expense/AddExpenseView.swift

[Warning] 495-495: File should contain 400 lines or less: currently contains 495

(file_length)

🔇 Additional comments (2)
Splito/UI/Home/Expense/AddExpenseViewModel.swift (2)

Line range hint 11-35: LGTM: Well-structured property declarations

The new properties for image handling are well-organized with appropriate access levels and follow SwiftUI conventions.


436-439: LGTM: Improved change detection logic

The change detection has been improved by removing the updatedBy field and correctly including the imageUrl comparison.

Splito/UI/Home/Expense/AddExpenseViewModel.swift Outdated Show resolved Hide resolved
@canopas canopas deleted a comment from coderabbitai bot Nov 22, 2024
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: 4

🧹 Outside diff range and nitpick comments (8)
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (3)

30-36: Consider adding loading and error states for image loading.

While the image view implementation is solid, consider enhancing the user experience by:

  • Adding a loading placeholder while the image loads
  • Handling potential image loading failures

Example implementation:

 if let imageUrl = viewModel.expense?.imageUrl {
     ExpenseImageView(showZoomableImageView: $showZoomableImageView, imageUrl: imageUrl)
+        .overlay {
+            AsyncImageLoadingView(url: imageUrl)
+        }
         .aspectRatio(16/9, contentMode: .fit)
         .cornerRadius(12)
 }

59-71: Simplify toolbar conditional logic.

The nested conditionals could be simplified for better readability.

Consider refactoring to:

-if viewModel.viewState != .loading {
-    if (viewModel.expense?.isActive ?? false) && (viewModel.group?.isActive ?? false) {
-        ToolbarItem(placement: .topBarTrailing) {
-            ToolbarButtonView(imageIcon: .binIcon, onClick: viewModel.handleDeleteButtonAction)
-        }
-        ToolbarItem(placement: .topBarTrailing) {
-            ToolbarButtonView(imageIcon: .editPencilIcon, onClick: viewModel.handleEditBtnAction)
-        }
-    } else {
-        ToolbarItem(placement: .topBarTrailing) {
-            RestoreButton(onClick: viewModel.handleRestoreButtonAction)
-        }
-    }
-}
+if viewModel.viewState == .loading { return }
+
+let isActiveState = (viewModel.expense?.isActive ?? false) && (viewModel.group?.isActive ?? false)
+ToolbarItem(placement: .topBarTrailing) {
+    if isActiveState {
+        HStack {
+            ToolbarButtonView(imageIcon: .binIcon, onClick: viewModel.handleDeleteButtonAction)
+            ToolbarButtonView(imageIcon: .editPencilIcon, onClick: viewModel.handleEditBtnAction)
+        }
+    } else {
+        RestoreButton(onClick: viewModel.handleRestoreButtonAction)
+    }
+}

74-77: Enhance image zoom transition animation.

The zoomable image view implementation could benefit from proper animation configuration.

Consider creating a shared namespace for smooth transitions:

 struct ExpenseDetailsView: View {
+    @Namespace private var imageNamespace
     
     // ... existing code ...
     
     .navigationDestination(isPresented: $showZoomableImageView) {
         if let imageUrl = viewModel.expense?.imageUrl {
-            ZoomableImageView(imageUrl: imageUrl, animationNamespace: Namespace())
+            ZoomableImageView(imageUrl: imageUrl, animationNamespace: imageNamespace)
+                .transition(.opacity.combined(with: .scale))
         }
     }
Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (3)

72-78: LGTM! Consider enhancing error handling feedback.

The async Task implementation for save action is well structured. However, consider providing more specific error feedback to users instead of a generic save failed error.

 Task {
     let isSucceed = await viewModel.handleSaveAction()
     if isSucceed {
         dismiss()
     } else {
-        viewModel.showSaveFailedError()
+        viewModel.showSaveFailedError(with: viewModel.errorMessage ?? "Failed to save payment")
     }
 }

185-185: Consider making date constraints configurable.

The maximum date is hardcoded to the current year. Consider making this configurable through an initializer parameter to improve reusability.

-    private let maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date()) ?? Date()
+    private let maximumDate: Date
+    
+    init(date: Binding<Date>, isForAddExpense: Bool = false, 
+         maximumDate: Date = Calendar.current.date(byAdding: .year, value: 0, to: Date()) ?? Date()) {
+        self._date = date
+        self.isForAddExpense = isForAddExpense
+        self._tempDate = State(initialValue: date.wrappedValue)
+        self.maximumDate = maximumDate
+    }

250-250: Add localization for "Today" string.

Consider localizing the "Today" string for better internationalization support.

-            Text(date.isToday() ? "Today" : date.shortDate)
+            Text(date.isToday() ? "Today".localized : date.shortDate)
Splito/UI/Home/Expense/AddExpenseView.swift (2)

109-141: Add accessibility identifiers for UI testing

The ExpenseInfoView contains important user input fields but lacks accessibility identifiers which are crucial for UI testing.

Consider adding accessibility identifiers to key elements:

 VStack(spacing: 16) {
     ExpenseDetailRow(name: $viewModel.expenseName, focusedField: focusedField, subtitle: "With you and:",
                      inputValue: viewModel.selectedGroup?.name ?? "Select group",
-                     showButton: true, onTap: viewModel.handleGroupBtnAction)
+                     showButton: true, onTap: viewModel.handleGroupBtnAction)
+                     .accessibilityIdentifier("group-selection-row")

     ExpenseDetailRow(name: $viewModel.expenseName, focusedField: focusedField,
-                     subtitle: "Description", field: .expenseName)
+                     subtitle: "Description", field: .expenseName)
+                     .accessibilityIdentifier("expense-description-row")

Line range hint 1-416: Consider splitting the file into smaller components

The file currently contains 416 lines, which exceeds the recommended maximum of 400 lines. Consider moving some components to separate files:

  • Move ZoomableImageView to a separate file
  • Move ExpenseImageView and ExpenseImagePickerView to a new file focused on image handling

This will improve maintainability and make the code easier to test.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 417-417: File should contain 400 lines or less: currently contains 417

(file_length)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4c4655d and 52635fb.

📒 Files selected for processing (3)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (4 hunks)
  • Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (3 hunks)
🔇 Additional comments (3)
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (2)

15-15: LGTM: State variable properly declared.

The state variable follows SwiftUI best practices and is appropriately scoped.


113-114: LGTM: Consistent styling applied.

The background and corner radius styling matches the design system and improves visual hierarchy.

Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (1)

155-177: LGTM! Clean implementation.

The PaymentDateRow implementation provides a good separation of concerns and consistent styling.

Splito/UI/Home/Expense/AddExpenseView.swift Outdated Show resolved Hide resolved
Splito/UI/Home/Expense/AddExpenseView.swift Outdated Show resolved Hide resolved
@canopas canopas deleted a comment from coderabbitai bot Nov 22, 2024
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 and nitpick comments (13)
Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift (2)

87-91: Consider adding input validation.

While the async/await refactoring is good, consider adding validation for the group name before proceeding with create/update operations.

 func handleDoneAction() async -> Bool {
+    guard !groupName.trimming(spaces: .leadingAndTrailing).isEmpty else {
+        showToastFor(toast: ToastPrompt(type: .error, title: "Error", message: "Group name cannot be empty"))
+        return false
+    }
     if let group {
         await updateGroup(group: group)
     } else {
         await createGroup()
     }
 }

Line range hint 99-143: Consider optimizing image handling.

While the image resizing and compression are good, consider:

  1. Moving image processing to a background queue to prevent UI blocking
  2. Implementing proper cleanup of image data after upload
 private func createGroup() async -> Bool {
     guard let userId = preference.user?.id else { return false }
     
     let memberBalance = GroupMemberBalance(id: userId, balance: 0, totalSummary: [])
     let group = Groups(name: groupName.trimming(spaces: .leadingAndTrailing), createdBy: userId, updatedBy: userId, imageUrl: nil,
                       members: [userId], balances: [memberBalance], createdAt: Timestamp(), updatedAt: Timestamp())

-    let resizedImage = profileImage?.aspectFittedToHeight(200)
-    let imageData = resizedImage?.jpegData(compressionQuality: 0.2)
+    let imageData = await Task.detached {
+        let resizedImage = self.profileImage?.aspectFittedToHeight(200)
+        return resizedImage?.jpegData(compressionQuality: 0.2)
+    }.value

     do {
         showLoader = true
         let group = try await groupRepository.createGroup(group: group, imageData: imageData)
+        // Clean up the image data
+        self.profileImage = nil
         NotificationCenter.default.post(name: .addGroup, object: group)
         showLoader = false
         return true
     } catch {
Splito/UI/Home/Expense/AddExpenseView.swift (2)

109-141: Consider extracting spacing constants and improving component separation

The ExpenseInfoView could benefit from:

  1. Extracting hardcoded spacing values into constants
  2. Breaking down into smaller, more focused components

Consider creating a separate spacing constants file:

enum Spacing {
    static let defaultPadding: CGFloat = 16
    static let rowSpacing: CGFloat = 16
    static let horizontalInset: CGFloat = 1
}

And refactoring the view to use these constants:

-VStack(spacing: 16) {
+VStack(spacing: Spacing.rowSpacing) {
     // ... content ...
 }
-.padding(.horizontal, 1)
+.padding(.horizontal, Spacing.horizontalInset)

303-416: Document gesture handling and extract magic numbers

The ZoomableImageView implementation is robust but would benefit from:

  1. Documentation explaining the gesture handling logic
  2. Extraction of magic numbers into named constants

Consider these improvements:

+/// Constants for zoom and pan limits
+private enum ZoomLimits {
+    static let minScale: CGFloat = 1.0
+    static let maxScale: CGFloat = 4.0
+    static let defaultDoubleTapScale: CGFloat = 3.0
+}

 struct ZoomableImageView: View {
     // ... existing properties ...

     private func fixOffsetAndScale(geometry: GeometryProxy) {
-        let newScale: CGFloat = .minimum(.maximum(scale, 1), 4)
+        let newScale: CGFloat = .minimum(.maximum(scale, ZoomLimits.minScale), ZoomLimits.maxScale)
         // ... rest of the implementation ...
     }

     private func resetZoom() {
-        scale = lastScale > 1 ? 1 : 3
+        scale = lastScale > ZoomLimits.minScale ? ZoomLimits.minScale : ZoomLimits.defaultDoubleTapScale
         // ... rest of the implementation ...
     }
 }
Splito/UI/Home/Expense/AddExpenseViewModel.swift (4)

201-248: Enhance user experience for camera permission handling.

While the implementation is generally good, consider these improvements for better user experience:

  1. The camera denied case could guide users better by explaining how to enable camera access in settings
  2. Consider handling the case when settings URL fails to open
 case .restricted, .denied:
-    showAlertFor(alert: .init(title: "Important!", message: "Camera access is required to take picture for expenses",
+    showAlertFor(alert: .init(title: "Camera Access Required", 
+                             message: "To take expense photos, please enable camera access in Settings > Privacy > Camera > Splito",
                               positiveBtnTitle: "Allow", positiveBtnAction: { [weak self] in
         if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
-            UIApplication.shared.open(settingsURL)
+            UIApplication.shared.open(settingsURL) { success in
+                if !success {
+                    self?.showToastFor(toast: ToastPrompt(type: .error, 
+                                                         title: "Error", 
+                                                         message: "Could not open Settings"))
+                }
+            }
         }
         self?.showAlert = false
     }))

313-322: Optimize member validation logic.

The validation logic can be made more efficient by using set operations instead of iterating multiple times.

 private func validateMembersInGroup(group: Groups, expense: Expense) -> Bool {
-    for payer in expense.paidBy where !group.members.contains(payer.key) {
-        return false
-    }
-    for memberId in expense.splitTo where !group.members.contains(memberId) {
-        return false
-    }
-
-    return true
+    let memberSet = Set(group.members)
+    let payerSet = Set(expense.paidBy.keys)
+    let splitToSet = Set(expense.splitTo)
+    
+    return memberSet.isSuperset(of: payerSet) && memberSet.isSuperset(of: splitToSet)
 }

367-369: Extract image processing constants.

Consider extracting magic numbers for image dimensions and compression quality into named constants for better maintainability.

+private enum ImageProcessing {
+    static let maxHeight: CGFloat = 200
+    static let compressionQuality: CGFloat = 0.2
+}

-let resizedImage = expenseImage?.aspectFittedToHeight(200)
-let imageData = resizedImage?.jpegData(compressionQuality: 0.2)
+let resizedImage = expenseImage?.aspectFittedToHeight(ImageProcessing.maxHeight)
+let imageData = resizedImage?.jpegData(compressionQuality: ImageProcessing.compressionQuality)

Also applies to: 415-417


436-441: Consider using Equatable for expense comparison.

The current comparison logic could be made more maintainable by implementing Equatable on the Expense struct and comparing relevant properties there.

+extension Expense: Equatable {
+    static func == (lhs: Expense, rhs: Expense) -> Bool {
+        return lhs.name == rhs.name &&
+               lhs.amount == rhs.amount &&
+               lhs.date.dateValue() == rhs.date.dateValue() &&
+               lhs.paidBy == rhs.paidBy &&
+               lhs.imageUrl == rhs.imageUrl &&
+               lhs.splitTo == rhs.splitTo &&
+               lhs.splitType == rhs.splitType &&
+               lhs.splitData == rhs.splitData &&
+               lhs.isActive == rhs.isActive
+    }
+}

 private func hasExpenseChanged(_ expense: Expense, oldExpense: Expense) -> Bool {
-    return oldExpense.name != expense.name || oldExpense.amount != expense.amount ||
-    oldExpense.date.dateValue() != expense.date.dateValue() || oldExpense.paidBy != expense.paidBy ||
-    oldExpense.imageUrl != expense.imageUrl || oldExpense.splitTo != expense.splitTo ||
-    oldExpense.splitType != expense.splitType || oldExpense.splitData != expense.splitData ||
-    oldExpense.isActive != expense.isActive
+    return expense != oldExpense
 }
Splito/Localization/Localizable.xcstrings (5)

783-791: Consider consolidating similar error messages.

There are multiple similar error messages for permissions and left group scenarios. Consider using a templated approach with placeholders for better maintainability.

-    "This expense could not be deleted. You do not have permission to delete this expense, Sorry!" : {
-      "extractionState" : "manual"
-    },
-    "This expense could not be edited. You do not have permission to edit this expense, Sorry!" : {
-      "extractionState" : "manual"
-    },
+    "action_not_allowed_%@_%@" : {
+      "extractionState" : "manual",
+      "comment": "First parameter is the entity (expense/payment), second is the action (delete/edit)"
+    },

804-812: Consolidate left group error messages.

Similar error messages for payments involving users who left the group can be consolidated into a single templated string.

-    "This payment involves a person who has left the group, and thus it can no longer be deleted. If you wish to change this payment, you must first add that person back to your group." : {
-      "extractionState" : "manual"
-    },
-    "This payment involves a person who has left the group, and thus it can no longer be edited. If you wish to change this payment, you must first add that person back to your group." : {
-      "extractionState" : "manual"
-    },
-    "This payment involves a person who has left the group, and thus it can no longer be restored. If you wish to change this payment, you must first add that person back to your group." : {
-      "extractionState" : "manual"
-    },
+    "payment_action_blocked_member_left_%@" : {
+      "extractionState" : "manual",
+      "comment": "Parameter is the action (delete/edit/restore)"
+    },

912-913: Consider adding comments for translators.

The restoration-related strings might benefit from translator comments explaining the context, especially for languages with different grammar structures.

     "You can restore it to recover all activities, expenses and payments." : {
-      "extractionState" : "manual"
+      "extractionState" : "manual",
+      "comment": "Shown when a group is deleted and can be restored"
     },
     "This will restore all activities, expenses and payments for this group." : {
-      "extractionState" : "manual"
+      "extractionState" : "manual",
+      "comment": "Confirmation message shown before restoring a group"
     },

Also applies to: 816-817


738-738: Improve clarity of empty state message.

The message "Start spending or receiving money to see your payments here." could be more engaging and informative.

-    "Start spending or receiving money to see your payments here." : {
+    "No payments yet! Record your first expense or payment to get started." : {
+      "extractionState" : "manual",
+      "comment": "Empty state message for payments list"
     },

142-146: Add comments for activity-related strings.

The "Activities" and "Activity" strings might need context for proper translation in languages with different plural forms.

     "Activities" : {
-      "extractionState" : "manual"
+      "extractionState" : "manual",
+      "comment": "Title for the activities list view (plural)"
     },
     "Activity" : {
+      "extractionState" : "manual",
+      "comment": "Title for a single activity item"
     },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 52635fb and 0e464a4.

📒 Files selected for processing (5)
  • Splito/Localization/Localizable.xcstrings (29 hunks)
  • Splito/UI/Home/Account/User Profile/UserProfileViewModel.swift (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (8 hunks)
  • Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Splito/UI/Home/Account/User Profile/UserProfileViewModel.swift
🔇 Additional comments (6)
Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift (3)

16-18: LGTM! Clean dependency injection setup.

The dependencies are well-organized and appropriate for the view model's responsibilities.


45-48: Improved camera permission handling with safe practices.

Good improvements:

  • Using DispatchQueue.main.async for UI updates
  • Safe optional binding for settings URL handling

Also applies to: 53-57


95-97: LGTM! Good error handling separation.

Clean implementation of error feedback mechanism.

Splito/UI/Home/Expense/AddExpenseView.swift (1)

92-98: 🛠️ Refactor suggestion

Enhance error handling in save action

The current implementation could benefit from more robust error handling to provide specific feedback to users.

Consider this improved implementation:

 Task {
-    let isActionSucceed = await viewModel.handleSaveAction()
-    if isActionSucceed {
-        dismiss()
-    } else {
-        viewModel.showSaveFailedError()
-    }
+    do {
+        let isActionSucceed = try await viewModel.handleSaveAction()
+        if isActionSucceed {
+            dismiss()
+        }
+    } catch {
+        viewModel.showSaveFailedError(with: error)
+    }
 }

Likely invalid or redundant comment.

Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

Line range hint 11-35: LGTM! Well-structured property declarations.

The new properties for image handling are well-organized with appropriate access levels and follow SwiftUI conventions.

Splito/Localization/Localizable.xcstrings (1)

236-240: LGTM: Camera access permission strings are well-defined.

The strings for camera access permissions are clear and specific to their use cases (expenses and profile pictures).

Splito/UI/Home/Expense/AddExpenseView.swift Outdated Show resolved Hide resolved
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 and nitpick comments (3)
Splito/UI/Home/Expense/AddExpenseView.swift (3)

162-176: Add accessibility support for text input

The text input implementation should include accessibility labels and hints for better VoiceOver support.

Consider these accessibility enhancements:

TextField("Enter a description", text: $name)
    .font(.subTitle2())
    .foregroundStyle(primaryText)
+    .accessibilityLabel("Expense description")
+    .accessibilityHint("Enter a description for your expense")
    .keyboardType(.default)

204-233: Add haptic feedback for better user interaction

Consider adding haptic feedback when the user interacts with the date picker or image selection buttons.

Add a feedback generator:

private struct BottomCardView: View {
+    private let feedback = UIImpactFeedbackGenerator(style: .light)
    
    // ... existing properties ...
    
    var body: some View {
        HStack(spacing: 16) {
            Spacer()
            
            DatePickerView(date: $date, isForAddExpense: true)
                .onTapGesture {
+                    feedback.impactOccurred()
                }
            
            ExpenseImagePickerView(image: expenseImage, imageUrl: expenseImageUrl)
                .onTapGesture {
+                    feedback.impactOccurred()
                    handleImageBtnTap()
                }
        }
    }
}

288-296: Optimize image caching strategy

The current Kingfisher implementation could benefit from additional caching options for better performance.

Consider these optimizations:

KFImage(url)
    .placeholder { _ in
        ImageLoaderView()
    }
    .setProcessor(DownsamplingImageProcessor(size: UIScreen.main.bounds.size))
    .cacheMemoryOnly()
+    .memoryCacheExpiration(.days(7))
+    .fadeTransition(.linear(0.3))
+    .onFailure { error in
+        print("Image loading failed: \(error.localizedDescription)")
+    }
    .resizable()
    .aspectRatio(contentMode: .fill)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0e464a4 and 31e9d5a.

📒 Files selected for processing (1)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
🔇 Additional comments (2)
Splito/UI/Home/Expense/AddExpenseView.swift (2)

109-141: LGTM! Well-structured view implementation

The ExpenseInfoView implementation is clean, consistent, and follows SwiftUI best practices with proper spacing and layout.


92-98: 🛠️ Refactor suggestion

Enhance error handling in save action

The current implementation could benefit from more specific error handling and user feedback.

Consider this improved implementation:

Task {
-    let isActionSucceed = await viewModel.handleSaveAction()
-    if isActionSucceed {
-        dismiss()
-    } else {
-        viewModel.showSaveFailedError()
-    }
+    do {
+        let isActionSucceed = try await viewModel.handleSaveAction()
+        if isActionSucceed {
+            dismiss()
+        }
+    } catch let error as ExpenseError {
+        viewModel.showSaveFailedError(with: error.localizedDescription)
+    } catch {
+        viewModel.showSaveFailedError(with: "Failed to save expense. Please try again.")
+    }
}

Likely invalid or redundant comment.

@canopas canopas deleted a comment from coderabbitai bot Nov 22, 2024
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 and nitpick comments (7)
Splito/UI/Home/Expense/AddExpenseView.swift (3)

78-82: Add error handling for image picker failures

While the image picker implementation is good, consider adding error handling for cases where the image picker fails or user denies camera/photo library permissions.

 .sheet(isPresented: $viewModel.showImagePicker) {
     ImagePickerView(cropOption: .square,
                     sourceType: !viewModel.sourceTypeIsCamera ? .photoLibrary : .camera,
-                    image: $viewModel.expenseImage, isPresented: $viewModel.showImagePicker)
+                    image: $viewModel.expenseImage,
+                    isPresented: $viewModel.showImagePicker,
+                    onError: { error in
+                        viewModel.handleImagePickerError(error)
+                    })
 }

303-332: Add loading state handling for image display

Consider adding a loading state indicator while the image is being prepared for display, especially for remote images.

 struct ImageDisplayView: View {
+    @State private var isLoading = false
     var image: UIImage?
     var imageUrl: String?
     
     @Namespace var animationNamespace
     
     var body: some View {
         GeometryReader { geometry in
             ZStack {
+                if isLoading {
+                    ProgressView()
+                        .progressViewStyle(.circular)
+                }
                 if #available(iOS 18.0, *) {
                     if let image {
                         ZoomableImageView(image: image, geometry: geometry)
                             .matchedGeometryEffect(id: "image", in: animationNamespace)
                             .navigationTransition(.zoom(sourceID: "zoom", in: animationNamespace))
                     } else if let imageUrl {
                         ZoomableImageView(imageUrl: imageUrl, geometry: geometry)
+                            .onAppear { isLoading = true }
+                            .onSuccess { _ in isLoading = false }
+                            .onFailure { _ in isLoading = false }
                             .matchedGeometryEffect(id: "image", in: animationNamespace)
                             .navigationTransition(.zoom(sourceID: "zoom", in: animationNamespace))
                     }

286-293: Optimize memory management for image loading

While Kingfisher's cacheMemoryOnly() is used, consider adding more aggressive memory management:

  1. Add memory warning handlers to clear the image cache
  2. Consider implementing a maximum cache size
  3. Add image size limits for uploads

Example implementation:

// Add to your app delegate or scene delegate
NotificationCenter.default.addObserver(
    forName: UIApplication.didReceiveMemoryWarningNotification,
    object: nil,
    queue: .main
) { _ in
    ImageCache.default.clearMemoryCache()
}

// Configure cache size
ImageCache.default.memoryStorage.config.totalCostLimit = 300 * 1024 * 1024 // 300MB
BaseStyle/BaseStyle/Views/ZoomableImageView.swift (4)

51-55: Avoid passing GeometryProxy to the initializer

Passing GeometryProxy as a parameter is unconventional in SwiftUI and may lead to issues. Consider using GeometryReader within the body to access geometry information.

Refactored code:

 public init(image: UIImage? = nil, imageUrl: String? = nil) {
     self.image = image
     self.imageUrl = imageUrl
-    self.geometry = geometry
 }

 public var body: some View {
+    GeometryReader { geometry in
         ZStack {
             // Use geometry.size where needed
         }
+    }
 }

59-95: Reduce code duplication in image view configuration

There is significant code duplication when configuring the image view for local and remote images. To improve maintainability, consider extracting common modifiers and gestures into a shared extension or applying them after the conditional blocks.

Example refactor:

public var body: some View {
    GeometryReader { geometry in
        let imageView: AnyView
        if let image = image {
            imageView = AnyView(
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            )
            loadedImage = image
        } else if let imageUrl = imageUrl, let url = URL(string: imageUrl) {
            imageView = AnyView(
                KFImage(url)
                    .placeholder { _ in
                        ImageLoaderView()
                    }
                    .setProcessor(DownsamplingImageProcessor(size: UIScreen.main.bounds.size))
                    .cacheMemoryOnly()
                    .onSuccess { result in
                        loadedImage = result.image
                    }
                    .resizable()
                    .scaledToFit()
            )
        } else {
            imageView = AnyView(EmptyView())
        }

        imageView
            .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
            .scaleEffect(scale, anchor: scaleAnchor)
            .offset(offset)
            .animation(.spring(), value: offset)
            .animation(.spring(), value: scale)
            .gesture(dragGesture)
            .gesture(magnificationGesture)
            .simultaneousGesture(
                TapGesture(count: 2).onEnded { _ in
                    resetZoom()
                }
            )
    }
}

124-124: Remove trailing whitespace

Line 124 contains trailing whitespace as indicated by the static analysis tool. Please remove the unnecessary whitespace to comply with code style guidelines.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 124-124: Lines should not have trailing whitespace

(trailing_whitespace)


23-23: Initialize loadedImage appropriately

The @State property loadedImage is initialized with an empty UIImage(), which may not be necessary and could lead to unintended behavior. Consider making loadedImage optional (UIImage?) and initializing it when needed.

Apply this change:

- @State var loadedImage: UIImage = UIImage()
+ @State var loadedImage: UIImage?

Ensure to handle cases where loadedImage might be nil in your code.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 31e9d5a and 9da0e43.

📒 Files selected for processing (4)
  • BaseStyle/BaseStyle.xcodeproj/project.pbxproj (5 hunks)
  • BaseStyle/BaseStyle/Views/ZoomableImageView.swift (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift
🧰 Additional context used
🪛 SwiftLint (0.57.0)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift

[Warning] 124-124: Lines should not have trailing whitespace

(trailing_whitespace)

🔇 Additional comments (4)
Splito/UI/Home/Expense/AddExpenseView.swift (2)

109-141: LGTM! Well-structured view composition

The ExpenseInfoView provides a clean separation of concerns and consistent UI structure using ExpenseDetailRow components.


11-11: LGTM! Consider specifying Kingfisher version

The integration of Kingfisher for image handling is appropriate. However, it's recommended to specify the version in the package dependencies to ensure consistency.

Also applies to: 38-41

BaseStyle/BaseStyle.xcodeproj/project.pbxproj (2)

12-12: LGTM: ZoomableImageView integration looks good.

The ZoomableImageView.swift file is properly integrated into the project structure and build phases, which aligns with the PR objective of adding expense attachment support.

Let's verify the implementation details:

Also applies to: 75-75, 509-509

✅ Verification successful

Implementation of ZoomableImageView is well-designed and complete

The implementation includes all necessary features for expense attachment viewing:

  • Proper zoom functionality with pinch-to-zoom gesture (1x to 4x scale)
  • Pan/drag gesture for moving zoomed images
  • Double-tap to toggle between normal and zoomed states
  • Handles both local UIImage and remote URLs via Kingfisher
  • Maintains image bounds within screen limits
  • Smooth animations and proper gesture handling
  • Image caching and downsampling for performance
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of ZoomableImageView
# Expected: SwiftUI view with zoom/pan gestures and image loading capabilities

# Search for the implementation
ast-grep --pattern 'struct ZoomableImageView: View {
  $$$
}'

# Check for gesture recognizers
rg -A 5 'gesture|zoom|scale|drag' ZoomableImageView.swift

# Check for image loading
rg -A 5 'Image|UIImage|URL' ZoomableImageView.swift

Length of output: 305


Script:

#!/bin/bash
# Let's first locate the ZoomableImageView.swift file
fd ZoomableImageView.swift

# Then check its content if found
fd ZoomableImageView.swift -x cat {}

Length of output: 5738


15-15: Clarify the purpose of RestoreButton.

While the file is properly integrated, its purpose seems unrelated to expense attachment functionality. Please clarify how this component contributes to the PR's objective.

Let's check its implementation and usage:

Also applies to: 78-78, 533-533

✅ Verification successful

RestoreButton is a reusable component for restoring deleted items

The RestoreButton component is a generic UI button used for restoring deleted items (both expenses and payments) in the application. Its implementation shows it's a simple SwiftUI button that displays "Restore" text and triggers a provided callback. In ExpenseDetailsView, it's used to implement the restore functionality for deleted expenses, which is relevant to the expense management functionality.

  • Used in ExpenseDetailsView with handleRestoreButtonAction that prompts a confirmation alert before restoring an expense
  • Also reused in transaction-related views for restoring payments
  • Implementation is clean and follows SwiftUI best practices
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Investigate RestoreButton's purpose and usage
# Expected: UI component potentially used in expense attachment flow

# Search for the implementation
ast-grep --pattern 'struct RestoreButton: View {
  $$$
}'

# Check for usage in expense-related files
rg -l 'RestoreButton' | rg -i 'expense'

Length of output: 228


Script:

#!/bin/bash
# Let's check the implementation and usage context of RestoreButton in expense-related files

# Check RestoreButton implementation
rg -A 10 "RestoreButton" ".*\.swift$"

# Specifically check its usage in expense detail files
rg -A 5 "RestoreButton" "ExpenseDetailsView.swift" "ExpenseDetailsViewModel.swift"

Length of output: 302


Script:

#!/bin/bash
# Let's try a different approach to find and analyze RestoreButton

# Find all Swift files containing RestoreButton
fd -e swift -x rg -A 10 "RestoreButton" {}

# Find the specific expense detail files
fd -e swift -x echo {} \; | grep -i "expense.*detail"

Length of output: 2623

#Conflicts:
#	Data/Data/Repository/GroupRepository.swift
#	Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift
#	Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift
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: 3

🧹 Outside diff range and nitpick comments (7)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift (4)

11-24: Add documentation for public API.

As this is a public struct, please add documentation comments explaining the purpose, parameters, and usage of the view.

Add documentation like this:

+/// A SwiftUI view that displays an image with zoom and pan capabilities.
+/// 
+/// Example usage:
+/// ```swift
+/// GeometryReader { geometry in
+///     ZoomableImageView(image: myImage, geometry: geometry)
+/// }
+/// ```
 public struct ZoomableImageView: View {

23-23: Consider making loadedImage optional.

Initialize loadedImage as an optional to better represent its state before an image is loaded.

-@State var loadedImage: UIImage = UIImage()
+@State var loadedImage: UIImage?

60-99: Reduce code duplication in view modifiers.

The gesture, animation, and positioning modifiers are duplicated between local and remote image views. Consider extracting them into a helper function.

+private func applyImageModifiers<T: View>(_ view: T) -> some View {
+    view
+        .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
+        .scaleEffect(scale, anchor: scaleAnchor)
+        .offset(offset)
+        .animation(.spring(), value: offset)
+        .animation(.spring(), value: scale)
+        .gesture(dragGesture)
+        .gesture(magnificationGesture)
+        .simultaneousGesture(TapGesture(count: 2).onEnded({ _ in
+            resetZoom()
+        }))
+}

 public var body: some View {
     ZStack {
         if let image = image {
-            Image(uiImage: image)
+            applyImageModifiers(Image(uiImage: image)
                 .resizable()
-                .scaledToFit()
-                .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
-                .scaleEffect(scale, anchor: scaleAnchor)
-                .offset(offset)
-                .animation(.spring(), value: offset)
-                .animation(.spring(), value: scale)
-                .gesture(dragGesture)
-                .gesture(magnificationGesture)
-                .simultaneousGesture(TapGesture(count: 2).onEnded({ _ in
-                    resetZoom()
-                })))
+                .scaledToFit())

127-127: Remove trailing whitespace.

Remove the trailing whitespace at the end of this line.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 127-127: Lines should not have trailing whitespace

(trailing_whitespace)

Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (2)

189-189: Simplify maximumDate calculation

The current calculation of maximumDate can be simplified. Adding 0 years is unnecessary.

-    private let maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date()) ?? Date()
+    private let maximumDate = Date()

211-215: Replace onTapGestureForced with standard gesture

Using forced tap gesture might interfere with accessibility features. Consider using the standard onTapGesture instead.

-        .onTapGestureForced {
+        .onTapGesture {
             tempDate = date
             showDatePicker = true
             UIApplication.shared.endEditing()
         }
Splito/UI/Home/Expense/AddExpenseView.swift (1)

204-233: Consider adding accessibility labels for better VoiceOver support

The bottom card implementation looks good, but could benefit from improved accessibility.

Add accessibility identifiers and labels:

 HStack(spacing: 16) {
     Spacer()

-    DatePickerView(date: $date, isForAddExpense: true)
+    DatePickerView(date: $date, isForAddExpense: true)
+        .accessibilityLabel("Expense date picker")

-    ExpenseImagePickerView(image: expenseImage, imageUrl: expenseImageUrl, handleImageBtnTap: handleExpenseImageTap)
+    ExpenseImagePickerView(image: expenseImage, imageUrl: expenseImageUrl, handleImageBtnTap: handleExpenseImageTap)
+        .accessibilityLabel("Expense image picker")
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9da0e43 and 680ba6d.

📒 Files selected for processing (4)
  • BaseStyle/BaseStyle/Views/ZoomableImageView.swift (1 hunks)
  • Data/Data/Repository/GroupRepository.swift (3 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (6 hunks)
  • Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Data/Data/Repository/GroupRepository.swift
🧰 Additional context used
🪛 SwiftLint (0.57.0)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift

[Warning] 127-127: Lines should not have trailing whitespace

(trailing_whitespace)

🔇 Additional comments (7)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift (1)

111-143: 🛠️ Refactor suggestion

Add null safety for loadedImage access.

If loadedImage is made optional as suggested earlier, ensure proper null safety in fixOffsetAndScale.

 private func fixOffsetAndScale(geometry: GeometryProxy) {
+    guard let image = loadedImage else { return }
     let newScale: CGFloat = .minimum(.maximum(scale, 1), 4)
     let screenSize = geometry.size

     let originalScale = loadedImage.size.width / loadedImage.size.height >= screenSize.width / screenSize.height ?
     geometry.size.width / loadedImage.size.width :
     geometry.size.height / loadedImage.size.height

Likely invalid or redundant comment.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 127-127: Lines should not have trailing whitespace

(trailing_whitespace)

Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (3)

158-158: Previous currency formatting improvements are still applicable

The previous review comment about enhancing currency formatting robustness is still valid. The current implementation needs improvements in decimal validation, maximum amount validation, and currency symbol handling.


248-268: LGTM! Clean implementation of date display

The DateDisplayView implementation is well-structured with good accessibility considerations and clear visual hierarchy.


216-245: Verify sheet presentation lifecycle

The sheet presentation might cause memory leaks if not properly managed. Let's verify the implementation across the codebase.

✅ Verification successful

Sheet presentation implementation is safe and follows SwiftUI lifecycle patterns

The verification of sheet presentations across the codebase shows:

  • All sheet presentations use isPresented binding, which properly manages the presentation lifecycle
  • The date picker sheet in GroupPaymentView follows the same pattern as other sheets in the codebase
  • Memory management is handled automatically by SwiftUI's view lifecycle:
    • Sheets are dismissed through binding state changes
    • Parent views properly clean up resources in onDisappear and deinit where needed
    • No manual retain cycle management is needed for simple sheet presentations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential memory leaks in sheet presentations
# Look for patterns where sheet modifiers are used without proper cleanup

# Search for sheet modifier usage
rg "\.sheet\(" -A 5

# Search for cleanup patterns
rg "onDisappear|deinit" -A 3

Length of output: 12617

Splito/UI/Home/Expense/AddExpenseView.swift (3)

11-11: LGTM! Good choice using Kingfisher for image handling

Kingfisher is an excellent choice for efficient image loading and caching.


303-346: 🛠️ Refactor suggestion

Consider implementing image cleanup on dismiss

The image display view handles layout and transitions well, but should manage memory explicitly.

Add cleanup logic when the view is dismissed:

 struct ImageDisplayView: View {
     @Environment(\.dismiss) var dismiss
 
     var image: UIImage?
     var imageUrl: String?
 
+    @State private var loadedImage: UIImage?
     @Namespace var animationNamespace
 
     var body: some View {
         GeometryReader { geometry in
             // ... existing implementation ...
         }
         .navigationBarBackButtonHidden()
+        .onDisappear {
+            // Clear loaded image when view disappears
+            loadedImage = nil
+        }
     }
 }

Likely invalid or redundant comment.


78-82: 🛠️ Refactor suggestion

Verify image size limits before upload

The image picker implementation should verify image sizes before upload to prevent performance issues.

Consider adding size validation:

 ImagePickerView(cropOption: .square,
     sourceType: !viewModel.sourceTypeIsCamera ? .photoLibrary : .camera,
-    image: $viewModel.expenseImage,
+    image: Binding(
+        get: { viewModel.expenseImage },
+        set: { newImage in
+            if let imageData = newImage?.jpegData(compressionQuality: 0.8),
+               imageData.count <= 5 * 1024 * 1024 { // 5MB limit
+                viewModel.expenseImage = newImage
+            } else {
+                // Show error for oversized image
+                viewModel.showAlert = true
+            }
+        }
+    ),
     isPresented: $viewModel.showImagePicker)

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: 0

🧹 Outside diff range and nitpick comments (11)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift (2)

60-99: Consider extracting common view modifiers to reduce duplication.

The view modifiers for both local and remote images are identical. Consider extracting them into a helper function or view extension.

Here's a suggested refactoring:

 public var body: some View {
     ZStack {
-        if let image = image {
-            Image(uiImage: image)
-                .resizable()
-                .scaledToFit()
-                .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
-                .scaleEffect(scale, anchor: scaleAnchor)
-                .offset(offset)
-                .animation(.spring(), value: offset)
-                .animation(.spring(), value: scale)
-                .gesture(dragGesture)
-                .gesture(magnificationGesture)
-                .simultaneousGesture(TapGesture(count: 2).onEnded({ _ in
-                    resetZoom()
-                }))
-        } else if let imageUrl = imageUrl, let url = URL(string: imageUrl) {
-            KFImage(url)
-                .placeholder { _ in
-                    ImageLoaderView()
-                }
-                .setProcessor(DownsamplingImageProcessor(size: UIScreen.main.bounds.size))
-                .cacheMemoryOnly()
-                .onSuccess { result in
-                    loadedImage = result.image
-                }
-                .resizable()
-                .scaledToFit()
-                .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
-                .scaleEffect(scale, anchor: scaleAnchor)
-                .offset(offset)
-                .animation(.spring(), value: offset)
-                .animation(.spring(), value: scale)
-                .gesture(dragGesture)
-                .gesture(magnificationGesture)
-                .simultaneousGesture(TapGesture(count: 2).onEnded({ _ in
-                    resetZoom()
-                }))
+        Group {
+            if let image = image {
+                Image(uiImage: image)
+                    .resizable()
+                    .scaledToFit()
+            } else if let imageUrl = imageUrl, let url = URL(string: imageUrl) {
+                KFImage(url)
+                    .placeholder { _ in
+                        ImageLoaderView()
+                    }
+                    .setProcessor(DownsamplingImageProcessor(size: UIScreen.main.bounds.size))
+                    .cacheMemoryOnly()
+                    .onSuccess { result in
+                        loadedImage = result.image
+                    }
+                    .resizable()
+                    .scaledToFit()
+            }
+        }
+        .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
+        .scaleEffect(scale, anchor: scaleAnchor)
+        .offset(offset)
+        .animation(.spring(), value: offset)
+        .animation(.spring(), value: scale)
+        .gesture(dragGesture)
+        .gesture(magnificationGesture)
+        .simultaneousGesture(TapGesture(count: 2).onEnded({ _ in
+            resetZoom()
+        }))
     }
 }

127-127: Remove trailing whitespace.

There is trailing whitespace on this line.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 127-127: Lines should not have trailing whitespace

(trailing_whitespace)

Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (2)

191-192: Optimize date picker state management

The current implementation:

  1. Uses separate tempDate state which might cause unnecessary redraws
  2. The DatePicker is recreated on each tempDate change due to the id modifier

Consider this optimization:

-    @State private var tempDate: Date
-    @State private var showDatePicker = false
+    @State private var showDatePicker = false {
+        didSet {
+            if showDatePicker {
+                tempDate = date
+            }
+        }
+    }
+    private var tempDate: Date = Date()

     // In the DatePicker view
-    DatePicker("", selection: $tempDate, in: ...maximumDate, displayedComponents: .date)
-        .id(tempDate)
+    DatePicker("", selection: $tempDate, in: ...maximumDate, displayedComponents: .date)

Also applies to: 227-232


254-262: Add accessibility support

The date display needs accessibility improvements:

  1. "Today" text should be localized
  2. Calendar icon needs accessibility label
-    Text(date.isToday() ? "Today" : date.shortDate)
+    Text(date.isToday() ? "Today".localized : date.shortDate)
     
     Image(.calendarIcon)
         .resizable()
         .scaledToFit()
         .frame(width: 24, height: 24)
+        .accessibility(label: Text("Calendar".localized))
Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsTabView.swift (1)

Line range hint 233-251: Consider refactoring for improved maintainability.

The initialization and value calculation logic could be simplified for better maintainability.

Consider these improvements:

  1. Group related parameters using a configuration struct:
struct MemberCellConfiguration {
    let suffixText: String
    let formatString: String
    let inputFieldWidth: Double
    // ... other configuration parameters
}
  1. Extract the calculated value logic into a computed property:
private var calculatedAmount: Double {
    guard let total = totalValue, total != 0 else { return 0 }
    return (expenseAmount * Double(value)) / total
}

This would make the code more maintainable and easier to test.

Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListView.swift (2)

60-60: Consider caching the sorted months.

The sorted(by: sortMonthYearStrings) operation is being performed twice - once for lastMonth and previously for firstMonth. This could be optimized by storing the sorted result.

-                            let firstMonth = viewModel.filteredTransactions.keys.sorted(by: sortMonthYearStrings).first
-                            let lastMonth = viewModel.filteredTransactions.keys.sorted(by: sortMonthYearStrings).last
+                            let sortedMonths = viewModel.filteredTransactions.keys.sorted(by: sortMonthYearStrings)
+                            let firstMonth = sortedMonths.first
+                            let lastMonth = sortedMonths.last

67-67: Extract the magic number into a constant.

The padding value of 50 should be defined as a named constant to improve code maintainability and make the purpose of this specific padding value clear.

+    private let lastItemBottomPadding: CGFloat = 50
+
     var body: some View {
         GeometryReader { geometry in
             ScrollViewReader { scrollProxy in

Then update the padding line:

-    .padding(.bottom, (month == lastMonth && viewModel.filteredTransactions[month]?.last?.transaction.id == transaction.transaction.id) ? 50 : 0)
+    .padding(.bottom, (month == lastMonth && viewModel.filteredTransactions[month]?.last?.transaction.id == transaction.transaction.id) ? lastItemBottomPadding : 0)
Splito/UI/Home/Expense/AddExpenseView.swift (4)

78-82: Consider enhancing image picker error handling

While the image picker implementation is functional, consider adding error handling for cases where the user denies camera/photo library access.

 .sheet(isPresented: $viewModel.showImagePicker) {
     ImagePickerView(cropOption: .square,
-                    sourceType: !viewModel.sourceTypeIsCamera ? .photoLibrary : .camera,
+                    sourceType: viewModel.sourceTypeIsCamera ? .camera : .photoLibrary,
                     image: $viewModel.expenseImage, isPresented: $viewModel.showImagePicker)
+        .onDisappear {
+            if viewModel.expenseImage == nil {
+                // Handle image selection failure or cancellation
+                viewModel.handleImageSelectionError()
+            }
+        }
 }

204-233: Add accessibility support for image selection

The BottomCardView implementation looks good, but consider adding accessibility labels and hints for the image selection functionality.

 ExpenseImagePickerView(image: expenseImage, imageUrl: expenseImageUrl, handleImageBtnTap: handleExpenseImageTap)
+    .accessibilityLabel("Expense image")
+    .accessibilityHint("Double tap to add or change expense image")

235-268: Enhance user experience with haptic feedback

Consider adding haptic feedback when the user taps the camera icon for a better tactile experience.

 .onTouchGesture {
+    UIImpactFeedbackGenerator(style: .light).impactOccurred()
     handleImageBtnTap()
 }

303-344: Add loading state for image display

Consider showing a loading indicator while the image is being prepared for display, especially for remote images.

 if let imageUrl {
     ZoomableImageView(imageUrl: imageUrl, geometry: geometry)
+        .overlay {
+            if viewModel.isLoadingImage {
+                ProgressView()
+                    .progressViewStyle(.circular)
+            }
+        }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9da0e43 and 67c4429.

📒 Files selected for processing (6)
  • BaseStyle/BaseStyle/Views/ZoomableImageView.swift (1 hunks)
  • Data/Data/Repository/GroupRepository.swift (3 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (6 hunks)
  • Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsTabView.swift (4 hunks)
  • Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (1 hunks)
  • Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListView.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Data/Data/Repository/GroupRepository.swift
🧰 Additional context used
🪛 SwiftLint (0.57.0)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift

[Warning] 127-127: Lines should not have trailing whitespace

(trailing_whitespace)

🔇 Additional comments (9)
BaseStyle/BaseStyle/Views/ZoomableImageView.swift (1)

26-49: Previous review comment is still applicable.

The gesture handlers should store their final values before calling fixOffsetAndScale.

Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift (3)

158-158: Enhance currency formatting implementation

The previous review comment about improving currency formatting robustness is still valid. The current implementation needs:

  1. Decimal point validation
  2. Maximum amount validation
  3. Better currency symbol handling

Please refer to the previous review comment for the detailed implementation suggestion.


173-173: Add missing isForAddExpense parameter

The DatePickerView is instantiated without the isForAddExpense parameter. While it has a default value, it should be explicitly set to false for payment flows to maintain consistency.


189-189: Review date range constraints

The maximum date constraint only considers the current year. Consider:

  1. Adding a minimum date constraint
  2. Making the date range configurable based on business requirements
Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsTabView.swift (2)

220-221: LGTM! Consistent implementation of user identification.

The SplitoPreference integration matches the implementation in ExpenseMemberCellView, maintaining consistency across the codebase.

Also applies to: 256-256


Line range hint 103-116: LGTM! Verify preference injection setup.

The personalization of member display using SplitoPreference is well implemented. The optional chaining in the comparison member.id == preference.user?.id safely handles the case when no user is set.

Let's verify the dependency injection setup:

✅ Verification successful

SplitoPreference injection is properly configured

The verification shows that SplitoPreference is correctly registered in the DI container (AppAssembly.swift) with container scope and is consistently injected across the app, including in ExpenseSplitOptionsTabView.swift. The implementation follows the established dependency injection pattern used throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if SplitoPreference is properly registered in the DI container
rg -A 5 "SplitoPreference" --type swift

Length of output: 24086

Splito/UI/Home/Expense/AddExpenseView.swift (3)

117-118: LGTM! Clean and consistent implementation

The ExpenseDetailRow usage follows a consistent pattern with proper parameter ordering and clear naming.

Also applies to: 121-122, 131-135


270-301: Previous review comment about error handling is still applicable


286-294: Verify memory usage patterns for large images

While Kingfisher's downsampling processor is used, it's important to verify memory usage patterns with large images.

✅ Verification successful

Let me gather more information about the image handling implementation to ensure a comprehensive verification.


Memory management for images follows consistent patterns

The codebase demonstrates a consistent and well-implemented approach to image memory management:

  • All image loading uses KFImage with DownsamplingImageProcessor to reduce memory footprint
  • Memory-only caching is consistently applied across image views
  • Custom UIImage extension includes size management utilities
  • ZoomableImageView and other image views follow the same memory optimization patterns

The implementation in AddExpenseView.swift aligns with the project's image handling best practices and doesn't require additional memory optimizations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other image processing implementations to ensure consistent memory management
rg -g '*.swift' -B 2 -A 2 'DownsamplingImageProcessor|UIImage.*scale|imageWith|CGImage'

Length of output: 1067


Script:

#!/bin/bash
# Search for KFImage usage patterns and caching configurations
rg -g '*.swift' -B 3 -A 3 'KFImage.*|ImageCache.*|\.cache'

# Look for any custom image processing or memory management utilities
rg -g '*.swift' -B 2 -A 2 'class.*Image|struct.*Image|extension.*Image'

Length of output: 11344

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants