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 notes support to expense #77

Merged
merged 1 commit into from
Nov 27, 2024
Merged

Conversation

cp-amisha-i
Copy link
Collaborator

@cp-amisha-i cp-amisha-i commented Nov 27, 2024

Simulator.Screen.Recording.-.iPhone.16.-.2024-11-27.at.18.35.29.mp4

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a note-taking feature for expenses, allowing users to add and manage notes during expense entry.
    • Added a dedicated view for adding notes with an intuitive interface.
    • Enhanced expense detail view to display associated notes.
  • Bug Fixes

    • Improved logic for detecting changes in expenses, including notes.
  • Localization

    • Added new localization strings for note-related functionalities.
  • UI Enhancements

    • Updated layouts and spacing in various views for a better user experience.
    • Introduced new icons for note representation in the app.

Copy link

coderabbitai bot commented Nov 27, 2024

Walkthrough

This pull request introduces enhancements to the expense management functionality within the application. Key changes include the addition of a note property to the Expense struct, updates to the ExpenseRepository for managing this new property, and the introduction of new views and view models for adding and displaying notes. Localization support has been expanded with new strings, and asset management has been updated to include new icons. These modifications facilitate the addition and management of notes associated with expenses.

Changes

File Change Summary
Data/Data/Model/Expense.swift - Added property: public var note: String?
- Updated initializer to include note parameter
- Updated CodingKeys enum to include case note = "note"
Data/Data/Repository/ExpenseRepository.swift - Updated method: hasExpenseChanged to compare note property
- Retained logic for image data in addExpense
- Updated deleteExpense to set isActive to false
- Modified updateExpenseWithImage to handle newImageUrl
Splito.xcodeproj/project.pbxproj - Added files: ExpenseAddNoteView.swift, ExpenseAddNoteViewModel.swift
- Created new group: Notes
Splito/Localization/Localizable.xcstrings - Added strings: "Add note", "Attachment:", "Failed to save note.", "Note:"
Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/Contents.json - Added assets: note-1_svgrepo.com.svg, note-1_svgrepo.com (1).svg
Splito/UI/Home/Expense/AddExpenseView.swift - Added method: handleNoteBtnTap in AddExpenseFooterView
- Added method: handleNoteBtnTap in NoteButtonView
- Added sheet presentation for note editor
Splito/UI/Home/Expense/AddExpenseViewModel.swift - Added properties: @Published var expenseNote: String, @Published var showAddNoteEditor = false
- Updated property signatures for expenseId and groupId to let
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift - Added struct: private struct ExpenseNoteView for displaying notes
- Introduced full-screen cover for note editor
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift - Added properties: @Published var expenseNote: String, @Published var showAddNoteEditor = false
- Added method: func handleNoteTap()
Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift - Added struct: ExpenseAddNoteView for adding notes
- Added struct: CancelButton for dismissing the view
Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift - Added class: ExpenseAddNoteViewModel for managing note state
- Added methods: func showSaveFailedError(), func handleSaveNoteAction() async -> Bool, private func updateExpenseNote() async -> Bool
- Added property: @Published var expenseNote: String

Possibly related PRs

  • Async await implementation fixes #64: The changes in AddExpenseViewModel.swift involve modifications related to the handling of expense notes, which directly connects to the addition of the note property in the Expense struct from the main PR.

🐰 In the meadow where expenses bloom,
A note is added, dispelling gloom.
With every tap, a story's told,
In the world of finances, brave and bold.
So hop along, let notes take flight,
In Splito's realm, all feels just right! 🌼✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d187779 and 0995e7d.

⛔ Files ignored due to path filters (2)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/note-1_svgrepo.com (1).svg is excluded by !**/*.svg
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/note-1_svgrepo.com.svg is excluded by !**/*.svg
📒 Files selected for processing (11)
  • Data/Data/Model/Expense.swift (2 hunks)
  • Data/Data/Repository/ExpenseRepository.swift (1 hunks)
  • Splito.xcodeproj/project.pbxproj (6 hunks)
  • Splito/Localization/Localizable.xcstrings (5 hunks)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/Contents.json (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (9 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (3 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift (3 hunks)
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift (1 hunks)
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • Data/Data/Model/Expense.swift
  • Data/Data/Repository/ExpenseRepository.swift
  • Splito.xcodeproj/project.pbxproj
  • Splito/Localization/Localizable.xcstrings
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/Contents.json
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift
🔇 Additional comments (3)
Splito/UI/Home/Expense/AddExpenseView.swift (3)

77-83: LGTM: Sheet presentation follows SwiftUI best practices

The note editor sheet implementation is well-structured and consistent with other sheet presentations in the file.


38-40: LGTM: Clean parameter addition and organization

The addition of note-related parameters and their organization is clean and logical.

Also applies to: 215-217


276-294: 🛠️ Refactor suggestion

Enhance accessibility and maintainability of NoteButtonView

Similar to previous feedback, the implementation needs improvements:

  1. Move keyboard dismissal logic to view model
  2. Add accessibility label for better VoiceOver support
  3. Extract hard-coded dimensions to constants
 private struct NoteButtonView: View {
     let handleNoteBtnTap: (() -> Void)
+    private let iconSize: CGFloat = 24
+    private let padding: CGFloat = 4
 
     var body: some View {
         Button {
-            UIApplication.shared.endEditing()
             handleNoteBtnTap()
         } label: {
             Image(.noteIcon)
                 .resizable()
                 .scaledToFit()
-                .frame(width: 24, height: 24)
-                .padding(4)
+                .frame(width: iconSize, height: iconSize)
+                .padding(padding)
                 .background(container2Color)
                 .cornerRadius(8)
+                .accessibilityLabel("Add note to expense")
         }
     }
 }

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

🧹 Outside diff range and nitpick comments (15)
Splito/UI/Home/Expense/Detail Selection/Notes/ExpenseAddNoteView.swift (2)

28-31: Add accessibility identifier to the TextEditor overlay.

The overlay rectangle should have an accessibility identifier to ensure proper VoiceOver support.

 .overlay {
     RoundedRectangle(cornerRadius: 12)
         .stroke(outlineColor, lineWidth: 1)
+        .accessibility(identifier: "noteTextEditorBorder")
 }

33-35: Simplify frame modifiers.

The double frame modifier can be combined into a single one for better readability.

-.frame(maxWidth: isIpad ? 600 : nil, alignment: .center)
-.frame(maxWidth: .infinity, alignment: .center)
+.frame(maxWidth: isIpad ? 600 : .infinity, alignment: .center)
Data/Data/Model/Expense.swift (1)

20-20: Consider adding note size validation

With the addition of notes, consider implementing size validation to prevent performance issues with very large notes. This could affect:

  • Memory usage when loading multiple expenses
  • Network bandwidth during sync
  • Database storage requirements

Consider adding validation in the appropriate service layer to enforce reasonable size limits.

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

Line range hint 1-150: Well-structured integration of the notes feature.

The note support has been cleanly integrated into the existing expense management flow:

  • Notes are properly tracked in change detection
  • Activity logging automatically captures note changes
  • The separation of concerns is maintained between notes, images, and core expense data

This approach ensures that notes are treated as first-class citizens in the expense tracking system while maintaining the existing architectural patterns.

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

31-42: Consider dynamic height constraints for image attachments

While the fixed height of 140 provides consistency, it might lead to image distortion or cropping for different aspect ratios. Consider using aspectRatio(contentMode:) modifier for better image presentation.

 ExpenseImageView(showImageDisplayView: $showImageDisplayView, imageUrl: imageUrl)
-    .frame(height: 140, alignment: .leading)
+    .frame(maxHeight: 140, alignment: .leading)
+    .aspectRatio(contentMode: .fit)
     .frame(maxWidth: .infinity)
     .cornerRadius(12)

66-72: Consider adding explicit dismiss handling

The fullScreenCover for note editing should handle explicit dismissal for better user experience.

 .fullScreenCover(isPresented: $viewModel.showAddNoteEditor) {
     NavigationStack {
         ExpenseAddNoteView(viewModel: ExpenseAddNoteViewModel(
             groupId: viewModel.groupId, expenseId: viewModel.expenseId, expenseNote: viewModel.expenseNote
-        ))
+        )).toolbar {
+            ToolbarItem(placement: .cancellationAction) {
+                Button("Cancel") {
+                    viewModel.showAddNoteEditor = false
+                }
+            }
+        }
     }
 }
Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift (2)

26-26: Consider grouping related sheet presentation states.

The property is well-defined, but consider grouping it with showEditExpenseSheet under a descriptive comment for better organization.

+    // MARK: - Sheet Presentation States
     @Published var showAddNoteEditor = false
     @Published var showEditExpenseSheet = false

23-26: Consider implementing state restoration for note editing.

When the note editor is dismissed, the view model should ensure that expenseNote is restored to its original value if editing was cancelled. This prevents stale state in the view model.

Consider adding:

  1. A backup of the original note value before editing
  2. State restoration logic when the editor is dismissed without saving
Splito/UI/Home/Expense/AddExpenseView.swift (1)

281-298: Enhance accessibility and platform independence

A few suggestions to improve this component:

  1. Add accessibility label for the note icon
  2. Consider moving UIKit-specific code to an extension or environment value

Consider these improvements:

 struct ExpenseNoteView: View {
     let handleNoteTap: (() -> Void)
     
     var body: some View {
         Image(.noteIcon)
             .resizable()
             .scaledToFit()
             .frame(width: 24, height: 24)
             .padding(4)
+            .accessibilityLabel("Add note")
             .onTouchGesture {
-                UIApplication.shared.endEditing()
+                hideKeyboard()
                 handleNoteTap()
             }
             .background(container2Color)
             .cornerRadius(8)
     }
+    
+    private func hideKeyboard() {
+        #if os(iOS)
+        UIApplication.shared.endEditing()
+        #endif
+    }
 }
Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

217-224: Consider adding input validation for notes

While the note handling functions are well-structured, consider adding:

  • Maximum length validation to prevent excessive note sizes
  • Trimming of whitespace (similar to how expenseName is handled)
  • Basic sanitization of input
func handleNoteSaveBtnTap(note: String) {
    showAddNoteEditor = false
-   self.expenseNote = note
+   let sanitizedNote = note.trimming(spaces: .leadingAndTrailing)
+   guard sanitizedNote.count <= 1000 else {
+       showToastFor(toast: ToastPrompt(
+           type: .warning,
+           title: "Note too long",
+           message: "Please keep your note under 1000 characters"
+       ))
+       return
+   }
+   self.expenseNote = sanitizedNote
}
Splito/Localization/Localizable.xcstrings (1)

224-225: Maintain consistent label formatting

The label strings "Attachment:" and "Note:" use colons, which might not be appropriate for all languages. Consider removing the colons and adding them in the UI layer if needed.

Apply this diff to make the labels consistent:

-    "Attachment:" : {
+    "Attachment" : {

-    "Note:" : {
+    "Note" : {

Also applies to: 512-513

Splito/UI/Home/Expense/Detail Selection/Notes/ExpenseAddNoteViewModel.swift (4)

48-62: Refactor showLoader handling using defer in fetchGroup()

The showLoader variable is set to false in multiple places within fetchGroup(). To avoid code duplication and ensure showLoader is turned off regardless of how the method exits, consider using a defer statement.

Apply this diff to refactor the method:

 private func fetchGroup() async -> Bool {
+        showLoader = true
+        defer { showLoader = false }
-        do {
-            showLoader = true
             self.group = try await groupRepository.fetchGroupBy(id: groupId)
             await fetchExpense()
-            showLoader = false
             LogD("ExpenseAddNoteViewModel: \(#function) Group fetched successfully.")
             return true
         } catch {
-            showLoader = false
             LogE("ExpenseAddNoteViewModel: \(#function) Failed to fetch group \(groupId): \(error).")
             showToastForError()
             return false
         }
     }

64-80: Use defer to manage showLoader in fetchExpense()

In fetchExpense(), showLoader = false is set in multiple paths. Utilizing a defer statement ensures showLoader is reset when the method exits, reducing redundancy.

Apply this diff to refactor the method:

 private func fetchExpense() async {
+        defer { showLoader = false }
         guard let expenseId else {
-            showLoader = false
             return
         }

         do {
             self.expense = try await expenseRepository.fetchExpenseBy(groupId: groupId, expenseId: expenseId)
             await updateExpense()
-            showLoader = false
             LogD("ExpenseAddNoteViewModel: \(#function) Expense fetched successfully.")
         } catch {
-            showLoader = false
             LogE("ExpenseAddNoteViewModel: \(#function) Failed to fetch expense \(expenseId): \(error).")
             showToastForError()
         }
     }

82-107: Simplify showLoader management in updateExpense() with defer

The showLoader variable is being set to false in several places in updateExpense(). To streamline the code, use a defer statement to ensure showLoader is reset when the method exits.

Apply this diff to refactor the method:

 private func updateExpense() async {
+        defer { showLoader = false }
         guard let group, let expense, let expenseId = expense.id else {
-            showLoader = false
             return
         }

         var updatedExpense = expense
         updatedExpense.note = expenseNote.trimmingCharacters(in: .whitespacesAndNewlines)

         guard expense.note != updatedExpense.note else {
-            showLoader = false
             return
         }

         do {
             try await expenseRepository.updateExpense(group: group, expense: updatedExpense, oldExpense: expense, type: .expenseUpdated)
             NotificationCenter.default.post(name: .updateExpense, object: updatedExpense)
-            showLoader = false
             LogD("ExpenseAddNoteViewModel: \(#function) Expense updated successfully.")
         } catch {
-            showLoader = false
             LogE("ExpenseAddNoteViewModel: \(#function) Failed to update expense \(expenseId): \(error).")
             showToastForError()
         }
     }

39-46: Simplify the handleSaveAction() method

The handleSaveAction() method can be streamlined by utilizing optional binding, which enhances readability and conciseness.

Apply this diff to refactor the method:

 func handleSaveAction() async -> Bool {
-        if handleSaveNoteTap != nil {
-            handleSaveNoteTap?(expenseNote)
-            return true
-        } else {
-            return await fetchGroup()
-        }
+        if let handleSaveNoteTap = handleSaveNoteTap {
+            handleSaveNoteTap(expenseNote)
+            return true
+        }
+        return await fetchGroup()
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 41f329e and b1e5cce.

⛔ Files ignored due to path filters (2)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/note-1_svgrepo.com (1).svg is excluded by !**/*.svg
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/note-1_svgrepo.com.svg is excluded by !**/*.svg
📒 Files selected for processing (11)
  • Data/Data/Model/Expense.swift (2 hunks)
  • Data/Data/Repository/ExpenseRepository.swift (1 hunks)
  • Splito.xcodeproj/project.pbxproj (6 hunks)
  • Splito/Localization/Localizable.xcstrings (3 hunks)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/Contents.json (1 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (9 hunks)
  • Splito/UI/Home/Expense/Detail Selection/Notes/ExpenseAddNoteView.swift (1 hunks)
  • Splito/UI/Home/Expense/Detail Selection/Notes/ExpenseAddNoteViewModel.swift (1 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (4 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Splito/Resource/Assets.xcassets/Images/Group/Add Expense/NoteIcon.imageset/Contents.json
🔇 Additional comments (17)
Splito/UI/Home/Expense/Detail Selection/Notes/ExpenseAddNoteView.swift (2)

11-17: LGTM! Well-structured state management.

The view properly manages its state using appropriate property wrappers. The temporary note storage pattern is a good practice for handling user input before committing changes.


14-14: Verify ViewModel memory management.

Ensure that the ViewModel doesn't create retain cycles with its parent view or hold strong references to delegates/callbacks.

✅ Verification successful

Based on the code inspection results, I can now provide a final response:

No memory management concerns found in the ViewModel implementation

The implementation shows good memory management practices:

  • The ViewModel uses value types for most properties (@published properties)
  • The closure handleSaveNoteTap is properly declared as optional and stored without capturing cycles
  • Dependencies are properly injected and don't create retain cycles
  • BaseViewModel implementation shows standard publisher management with cancelable Set
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential retain cycles in ViewModel
ast-grep --pattern 'class ExpenseAddNoteViewModel {
  $$$
  var $x: $t
  $$$
}'

Length of output: 81


Script:

#!/bin/bash
# Let's try a different approach to find the ViewModel implementation
rg -l "ExpenseAddNoteViewModel" --type swift

# Then let's look at its content
rg "class ExpenseAddNoteViewModel" -A 20 --type swift

Length of output: 2721


Script:

#!/bin/bash
# Let's check the BaseViewModel implementation for any potential retain cycle patterns
rg "class BaseViewModel" -A 10 --type swift

# Also check for any closure handling in the ViewModel
rg "func.*saveNote" -A 10 --type swift ExpenseAddNoteViewModel.swift

Length of output: 813

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

52-52: Verify the intentional difference between property name and JSON key

The property name note (singular) maps to JSON key "notes" (plural). While this works, it creates a slight inconsistency in naming. Please verify if this mapping is intentional to match existing API contracts.

✅ Verification successful

Based on the codebase analysis, I can now provide a final response regarding the note vs notes naming difference:

The singular/plural difference is intentional and follows the codebase's data model design

The property name note in the Swift model correctly maps to the "notes" JSON key as it follows the established pattern in the codebase where:

  • The Swift property note represents a single note string in the Expense model
  • The Firebase/JSON field uses "notes" as the storage key
  • The codebase consistently uses this mapping through the expense creation and editing flows
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any existing usage of "notes" key in JSON responses or API documentation
rg -l "\"notes\"" --type json
rg -l "\"notes\":" --type json

Length of output: 60


Script:

#!/bin/bash
# Search for "notes" field usage in Swift files to understand the API contract
rg -i "notes" --type swift -C 2

# Search for any JSON mock data or test files that might contain "notes"
fd -e json -x cat {}

Length of output: 63500


Script:

#!/bin/bash
# Search for the Expense model structure and any related API/backend code
rg -A 10 -B 10 "struct Expense" --type swift

# Search for any Firebase or backend schema definitions that might show the API contract
fd -e rules -e schema -x cat {}

Length of output: 36647

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

74-77: LGTM! The note comparison is properly integrated.

The hasExpenseChanged method has been correctly updated to include the note property comparison, maintaining consistency with the expense model changes.


Line range hint 1-150: Verify activity logging for note changes.

While the note field is properly integrated into change detection, we should verify that note changes are appropriately reflected in activity logs for proper user communication.

✅ Verification successful

Note changes are properly handled in the expense tracking system

The implementation is complete and properly handles note changes:

  1. Note changes are captured in hasExpenseChanged() method through the comparison oldExpense.note != expense.note
  2. When note changes occur, they are automatically included in expense updates through the standard expense update flow
  3. Activity logging for note changes is handled through the generic expenseUpdated activity type, which is appropriate since:
    • Notes are supplementary information that don't require special activity type
    • Changes are tracked alongside other expense properties
    • The activity log focuses on essential financial information (amount, split, etc.)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for note-related activity logging
rg -A 5 "note.*activity|activity.*note" 

# Check if there are any specific activity types for note changes
ast-grep --pattern 'enum ActivityType {
  $$$
}'

Length of output: 92


Script:

#!/bin/bash
# Let's first check the ActivityType enum definition
fd -e swift ActivityType

Length of output: 26


Script:

#!/bin/bash
# Let's try finding the enum definition using ripgrep
rg "enum ActivityType" -A 10

# Also search for any activity-related types or models
rg "class ActivityLog|struct ActivityLog" -A 10

Length of output: 9188

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

232-232: LGTM!

The padding adjustment improves the visual hierarchy of the expense information section.


Line range hint 25-72: Verify state handling across view updates

Please ensure that the view properly handles all possible state combinations:

  1. Loading state while fetching expense details
  2. Note editor state while showing image viewer
  3. Edit expense state while showing note editor
✅ Verification successful

State handling implementation appears safe and well-structured

The verification shows that the state management is properly implemented:

  1. Both showAddNoteEditor and showImageDisplayView are initialized as false by default
  2. showAddNoteEditor is managed in the ViewModel while showImageDisplayView is managed as local view state
  3. The states are toggled independently and have clear reset logic
  4. The fullScreenCover modifiers in the view ensure proper modal presentation hierarchy
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential state handling issues in view model

# Check for state management in view model
ast-grep --pattern 'class $_ {
  $$$
  @Published var showAddNoteEditor: Bool
  $$$
  @Published var showImageDisplayView: Bool
  $$$
}'

# Check for concurrent state updates
rg -A 3 'showAddNoteEditor.*=.*true|showImageDisplayView.*=.*true'

Length of output: 1347


Script:

#!/bin/bash
# Let's check how these state variables are managed and if there are any safeguards

# Check view model implementation for state handling
ast-grep --pattern 'class ExpenseDetailsViewModel {
  $$$
}'

# Check for any state reset logic
rg -A 3 'showAddNoteEditor.*=.*false|showImageDisplayView.*=.*false'

# Check for any state dependencies or mutual exclusivity handling
rg -A 5 'showAddNoteEditor|showImageDisplayView' 'Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift'

Length of output: 2338

Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift (3)

23-25: LGTM! Properties are well-defined with appropriate access levels.

The new properties follow the established pattern in the codebase with proper access modifiers.


96-96: LGTM! Note assignment is properly placed.

The note assignment is correctly integrated into the expense processing flow.


114-114: Skip commenting on cosmetic change.

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

38-40: LGTM: Footer view parameters are well-structured

The parameter additions and ordering are clean and follow good SwiftUI practices.


77-85: Consider safer handling of optional values

While the optional binding is good, the view might not handle the case where groupId or expenseId is nil. Consider providing a fallback or error state.

Let's verify the handling of nil cases:

Consider this safer approach:

 .sheet(isPresented: $viewModel.showAddNoteEditor) {
     NavigationStack {
-        if let groupId = viewModel.groupId, let expenseId = viewModel.expenseId {
-            ExpenseAddNoteView(viewModel: ExpenseAddNoteViewModel(
-                groupId: groupId, expenseId: expenseId, expenseNote: viewModel.expenseNote, handleSaveNoteTap: viewModel.handleNoteSaveBtnTap(note:)
-            ))
-        }
+        Group {
+            if let groupId = viewModel.groupId, let expenseId = viewModel.expenseId {
+                ExpenseAddNoteView(viewModel: ExpenseAddNoteViewModel(
+                    groupId: groupId, expenseId: expenseId, expenseNote: viewModel.expenseNote, handleSaveNoteTap: viewModel.handleNoteSaveBtnTap(note:)
+                ))
+            } else {
+                ContentUnavailableView("Unable to add note", systemImage: "note.text.badge.exclamationmark")
+            }
+        }
     }
 }
Splito/UI/Home/Expense/AddExpenseViewModel.swift (3)

22-22: LGTM: Property declarations for note support

The new properties are properly declared with appropriate types and access levels.

Also applies to: 35-35


55-56: Great improvement: Making identifiers immutable

Converting expenseId and groupId to let constants improves code safety by preventing accidental modifications after initialization.


377-378: Consider standardizing empty note handling

The code handles notes consistently across create and update operations, but consider standardizing empty note handling:

  • Should empty notes be stored as empty strings or null?
  • Should whitespace-only notes be treated as empty?

Let's check how empty notes are handled in the Expense model:

Also applies to: 419-419, 466-468

✅ Verification successful

Let's also search for note-related operations:

And check for any null handling patterns:


Based on the search results, I can now provide a final response:

Empty note handling is already standardized

The codebase shows a consistent approach to note handling:

  • Notes are stored as non-optional String in the Expense model
  • Notes are trimmed before saving using trimming(spaces: .leadingAndTrailing)
  • Empty notes are handled as empty strings rather than null values
  • Note comparison for changes is done with direct string equality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Expense model definition and note handling
ast-grep --pattern 'struct Expense {
  $$$
  var note: $_
  $$$
}'

Length of output: 68


Script:

#!/bin/bash
# Search for Expense model definition with a more flexible pattern
rg -l "struct\s+Expense" --type swift

# After finding the files, let's examine their content
rg "struct\s+Expense" -A 20 --type swift

Length of output: 37133


Script:

#!/bin/bash
# Search for note property and its usage
rg "note\s*:" --type swift -A 2 -B 2

Length of output: 3525


Script:

#!/bin/bash
# Search for nil or optional handling related to notes
rg "note\s*[?!]|\?\?.*note|note.*\?\?" --type swift

Length of output: 454

Splito.xcodeproj/project.pbxproj (2)

158-159: LGTM: New files are properly organized in a dedicated group.

The new note-related files are well-organized in a dedicated "Notes" group, following good project structure practices.

Also applies to: 316-324


1077-1077: LGTM: Build phases and group hierarchy are correctly configured.

The new files are:

  • Properly added to the compilation sources
  • Correctly integrated into the "Detail Selection" group hierarchy
  • Set up with appropriate file references

Also applies to: 1107-1107, 521-521

Data/Data/Model/Expense.swift Outdated Show resolved Hide resolved
Data/Data/Model/Expense.swift Outdated Show resolved Hide resolved
Splito/Localization/Localizable.xcstrings 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: 6

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

12-22: Consider making required properties non-optional.

The group and expense properties are marked as optional but are required for the core update functionality (as seen in updateExpenseNote()). Consider making them non-optional to enforce this requirement at compile-time and reduce optional unwrapping.

-    private let group: Groups?
-    private let expense: Expense?
+    private let group: Groups
+    private let expense: Expense

23-29: Add validation for required parameters.

If group and expense are required for core functionality, consider adding validation in the initializer.

-    init(group: Groups?, expense: Expense?, expenseNote: String, handleSaveNoteTap: ((String) -> Void)? = nil) {
+    init(group: Groups, expense: Expense, expenseNote: String, handleSaveNoteTap: ((String) -> Void)? = nil) {
         self.group = group
         self.expense = expense
         self.expenseNote = expenseNote
         self.handleSaveNoteTap = handleSaveNoteTap
         super.init()
     }

32-34: Use localized strings for error messages.

The error message is hardcoded. Consider using a localized string for better internationalization support.

-        self.showToastFor(toast: ToastPrompt(type: .error, title: "Oops", message: "Failed to save note."))
+        self.showToastFor(toast: ToastPrompt(type: .error, title: L10n.error, message: L10n.failedToSaveNote))
Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift (3)

16-17: Consider initializing tempNote with viewModel.expenseNote

While the note is set in onAppear, initializing tempNote with an empty string could potentially lead to data loss if onAppear fails to execute. Consider initializing it with viewModel.expenseNote directly.

-    @State private var tempNote: String = ""
+    @State private var tempNote: String
+
+    init(viewModel: ExpenseAddNoteViewModel) {
+        _viewModel = StateObject(wrappedValue: viewModel)
+        _tempNote = State(initialValue: viewModel.expenseNote)
+    }

75-75: Use design system color instead of direct color reference

For consistency with the design system, use a color from BaseStyle instead of direct color reference.

-        .foregroundStyle(.blue)
+        .foregroundStyle(accentColor)

21-31: Add accessibility support

Consider enhancing the TextField with accessibility modifiers for better VoiceOver support.

             TextField("", text: $tempNote, prompt: Text(""), axis: .vertical)
+                .accessibilityLabel(LocalizedStringKey("expense.note.accessibility.label"))
+                .accessibilityHint(LocalizedStringKey("expense.note.accessibility.hint"))
Splito/UI/Home/Expense/AddExpenseView.swift (2)

77-83: Consider adding explicit dismiss handling

While the sheet presentation is well-implemented, consider adding explicit dismiss handling to ensure proper cleanup when the sheet is dismissed. This could be important if there are any temporary states that need to be reset.

 .sheet(isPresented: $viewModel.showAddNoteEditor) {
     NavigationStack {
         ExpenseAddNoteView(viewModel: ExpenseAddNoteViewModel(group: viewModel.selectedGroup, expense: viewModel.expense,
                                                               expenseNote: viewModel.expenseNote,
-                                                              handleSaveNoteTap: viewModel.handleNoteSaveBtnTap(note:)))
+                                                              handleSaveNoteTap: viewModel.handleNoteSaveBtnTap(note:)))
+            .onDisappear {
+                // Reset any temporary states if needed
+            }
     }
 }

Line range hint 1-293: Add documentation for the new note functionality

Consider adding documentation comments to describe:

  • The purpose and usage of the note feature
  • The interaction flow between components
  • Any important state management details

This will help maintain the codebase and assist other developers in understanding the implementation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b1e5cce and 9ef3a9b.

📒 Files selected for processing (7)
  • Splito.xcodeproj/project.pbxproj (6 hunks)
  • Splito/Localization/Localizable.xcstrings (4 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (4 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift (3 hunks)
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift (1 hunks)
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • Splito/Localization/Localizable.xcstrings
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift
🔇 Additional comments (6)
Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift (1)

1-11: LGTM! File structure and imports are appropriate.

The imports are minimal and necessary for the functionality.

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

38-40: LGTM: Parameter addition is well-structured

The new handleNoteTap parameter is properly integrated into the existing parameter list, with good formatting for readability.


215-215: LGTM: Footer view integration is clean

The note functionality is well-integrated into the footer view, maintaining consistent spacing and layout with existing elements.

Also applies to: 230-231

Splito.xcodeproj/project.pbxproj (3)

158-159: LGTM: File references properly added

The new files follow the project's MVVM pattern naming convention and are correctly referenced with proper Swift file types.


316-324: LGTM: Group structure properly organized

The new "Notes" group is correctly placed under the Expense section and follows the project's organizational structure, keeping related files together.

Also applies to: 511-511


1077-1077: LGTM: Build phases correctly updated

The new files are properly added to the compilation sources, ensuring they will be included in the build process.

Also applies to: 1107-1107

@canopas canopas deleted a comment from coderabbitai bot Nov 27, 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: 0

🧹 Outside diff range and nitpick comments (2)
Splito/UI/Home/Expense/AddExpenseView.swift (1)

38-40: Improve parameter organization in AddExpenseFooterView initialization

The parameters should be organized more consistently for better readability. Consider grouping related parameters together and aligning them properly.

-AddExpenseFooterView(date: $viewModel.expenseDate, showImagePickerOptions: $viewModel.showImagePickerOptions,
-                     expenseImage: viewModel.expenseImage, expenseImageUrl: viewModel.expenseImageUrl,
-                     handleNoteBtnTap: viewModel.handleNoteBtnTap, handleExpenseImageTap: viewModel.handleExpenseImageTap,
-                     handleActionSelection: viewModel.handleActionSelection(_:))
+AddExpenseFooterView(
+    // State bindings
+    date: $viewModel.expenseDate,
+    showImagePickerOptions: $viewModel.showImagePickerOptions,
+    // Image properties
+    expenseImage: viewModel.expenseImage,
+    expenseImageUrl: viewModel.expenseImageUrl,
+    // Action handlers
+    handleNoteBtnTap: viewModel.handleNoteBtnTap,
+    handleExpenseImageTap: viewModel.handleExpenseImageTap,
+    handleActionSelection: viewModel.handleActionSelection(_:)
+)
Splito/UI/Home/Expense/AddExpenseViewModel.swift (1)

378-378: Consider adding note content validation

While the note integration is correct, consider adding validation for the note content:

  • Maximum length limits
  • Content sanitization
  • Handling of special characters

This would prevent potential issues with storage or display of the notes.

 private func handleAddExpenseAction(userId: String, group: Groups) async -> Bool {
+    let sanitizedNote = expenseNote.trimmingCharacters(in: .whitespacesAndNewlines)
+    guard sanitizedNote.count <= 1000 else {
+        showToastFor(toast: ToastPrompt(type: .warning, 
+            title: "Note too long", 
+            message: "Please keep the note under 1000 characters"))
+        return false
+    }
     let expense = Expense(name: expenseName.trimming(spaces: .leadingAndTrailing), 
         amount: expenseAmount,
         date: Timestamp(date: expenseDate), 
         paidBy: selectedPayers, 
         addedBy: userId, 
         updatedBy: userId,
-        note: expenseNote,
+        note: sanitizedNote,
         splitTo: (splitType == .equally) ? selectedMembers : splitData.map({ $0.key }),
         splitType: splitType, 
         splitData: splitData)

Also applies to: 418-418

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef3a9b and 980073b.

📒 Files selected for processing (8)
  • Data/Data/Model/Expense.swift (2 hunks)
  • Splito/Localization/Localizable.xcstrings (5 hunks)
  • Splito/UI/Home/Expense/AddExpenseView.swift (5 hunks)
  • Splito/UI/Home/Expense/AddExpenseViewModel.swift (9 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift (3 hunks)
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift (3 hunks)
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift (1 hunks)
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • Data/Data/Model/Expense.swift
  • Splito/Localization/Localizable.xcstrings
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift
  • Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsViewModel.swift
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteView.swift
  • Splito/UI/Home/Expense/Notes/ExpenseAddNoteViewModel.swift
🔇 Additional comments (7)
Splito/UI/Home/Expense/AddExpenseView.swift (3)

77-83: LGTM! Sheet presentation follows SwiftUI best practices

The note editor sheet presentation is well-implemented with proper view model dependency injection.


215-215: LGTM! Clean integration of note functionality

The note button is well-integrated into the footer view with proper parameter passing.

Also applies to: 231-231


276-294: 🛠️ Refactor suggestion

Enhance accessibility and maintainability of NoteButtonView

The implementation needs improvements in several areas:

  1. Move keyboard dismissal logic to the view model
  2. Add accessibility label for better VoiceOver support
  3. Extract hard-coded dimensions to constants
 private struct NoteButtonView: View {
+    private let iconSize: CGFloat = 24
+    private let iconPadding: CGFloat = 4
     let handleNoteBtnTap: (() -> Void)
 
     var body: some View {
         Button {
-            UIApplication.shared.endEditing()
             handleNoteBtnTap()
         } label: {
             Image(.noteIcon)
                 .resizable()
                 .scaledToFit()
-                .frame(width: 24, height: 24)
-                .padding(4)
+                .frame(width: iconSize, height: iconSize)
+                .padding(iconPadding)
                 .background(container2Color)
                 .cornerRadius(8)
+                .accessibilityLabel("Add note to expense")
         }
     }
 }
Splito/UI/Home/Expense/AddExpenseViewModel.swift (4)

22-22: LGTM: Property declarations for note support

The new properties are properly declared with appropriate default values and access levels.

Also applies to: 31-31


55-56: Good improvement: Enhanced immutability

Converting expenseId and groupId from var to let improves thread safety and enforces proper initialization semantics.


217-224: LGTM: Clean implementation of note handling methods

The methods are concise and follow the single responsibility principle.


465-467: LGTM: Proper integration of note change detection

The note comparison is correctly integrated into the change detection logic, maintaining consistency with the existing implementation.

@cp-amisha-i cp-amisha-i force-pushed the allow-to-add-notes-with-expense branch from d187779 to 0995e7d Compare November 27, 2024 14:34
@cp-amisha-i cp-amisha-i merged commit 2e682bc into main Nov 27, 2024
2 checks passed
@cp-amisha-i cp-amisha-i deleted the allow-to-add-notes-with-expense branch November 28, 2024 06:36
@coderabbitai coderabbitai bot mentioned this pull request Dec 17, 2024
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.

1 participant