Skip to content

Commit

Permalink
Add reminder support
Browse files Browse the repository at this point in the history
Co-authored-by: Amisha <amisha.i@canopas.com>
  • Loading branch information
cp-nirali-s and cp-amisha-i authored Dec 30, 2024
1 parent 3a51c7b commit fac97c3
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 20 deletions.
12 changes: 12 additions & 0 deletions Data/Data.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
21559CA42CBD05570039F127 /* ActivityLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21559CA32CBD05570039F127 /* ActivityLog.swift */; };
21559CAE2CBD2AED0039F127 /* ActivityLogStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21559CAD2CBD2AED0039F127 /* ActivityLogStore.swift */; };
21559CB02CBD2B400039F127 /* ActivityLogRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21559CAF2CBD2B400039F127 /* ActivityLogRepository.swift */; };
21CF56512D1E804000B47A6D /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CF56502D1E804000B47A6D /* DeepLinkManager.swift */; };
21D8D0832C0857F10061B365 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D8D0822C0857F10061B365 /* Constants.swift */; };
64E499C5CFAEA368EC21313F /* Pods_DataTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1571EA0A08D442FEF7C09424 /* Pods_DataTests.framework */; };
7EF3A291581F7EA20CB1042D /* Pods_Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E91B3E23688435064A60C0C4 /* Pods_Data.framework */; };
Expand Down Expand Up @@ -70,6 +71,7 @@
21559CA32CBD05570039F127 /* ActivityLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityLog.swift; sourceTree = "<group>"; };
21559CAD2CBD2AED0039F127 /* ActivityLogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityLogStore.swift; sourceTree = "<group>"; };
21559CAF2CBD2B400039F127 /* ActivityLogRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityLogRepository.swift; sourceTree = "<group>"; };
21CF56502D1E804000B47A6D /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = "<group>"; };
21D8D0822C0857F10061B365 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
5B14CF1A2EEF27479BF50566 /* Pods-DataTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DataTests.debug.xcconfig"; path = "Target Support Files/Pods-DataTests/Pods-DataTests.debug.xcconfig"; sourceTree = "<group>"; };
803094012FE4C155F2A347B6 /* Pods-DataTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DataTests.release.xcconfig"; path = "Target Support Files/Pods-DataTests/Pods-DataTests.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -139,6 +141,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
21CF564F2D1E802E00B47A6D /* Deeplink */ = {
isa = PBXGroup;
children = (
21CF56502D1E804000B47A6D /* DeepLinkManager.swift */,
);
path = Deeplink;
sourceTree = "<group>";
};
9E4388A4C8057257EE82C256 /* Pods */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -312,6 +322,7 @@
D83B15072B99976F004A5F4F /* Repository */,
D8A7CA7E2BA867C80014EC67 /* Extension */,
D89DBE4B2B8CBEB500E5F1BD /* Services */,
21CF564F2D1E802E00B47A6D /* Deeplink */,
D8AC25BC2B7F357A00CEAAD3 /* Helper */,
);
path = Data;
Expand Down Expand Up @@ -519,6 +530,7 @@
D8AC25C12B7F38D300CEAAD3 /* Injector.swift in Sources */,
D8D14A542BA092F500F45FF2 /* ShareCodeRepository.swift in Sources */,
D8A7CA7B2BA5B6AC0014EC67 /* ShareCodeStore.swift in Sources */,
21CF56512D1E804000B47A6D /* DeepLinkManager.swift in Sources */,
D865F8AE2BD7CB0B0084BD36 /* Array+Extension.swift in Sources */,
21559CB02CBD2B400039F127 /* ActivityLogRepository.swift in Sources */,
D89DBE2B2B88817E00E5F1BD /* JSONUtils.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Data/Data/DI/AppAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,9 @@ public class AppAssembly: Assembly {
container.register(TransactionRepository.self) { _ in
TransactionRepository.init()
}.inObjectScope(.container)

container.register(DeepLinkManager.self) { _ in
DeepLinkManager()
}.inObjectScope(.container)
}
}
37 changes: 37 additions & 0 deletions Data/Data/Deeplink/DeepLinkManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// DeepLinkManager.swift
// Data
//
// Created by Amisha Italiya on 27/12/24.
//

import Foundation

public enum DeepLinkType: Equatable {
case group(groupId: String)

var key: String {
switch self {
case .group:
return "group"
}
}
}

public class DeepLinkManager: ObservableObject {

@Published public var type: DeepLinkType?

public init() {}

public func handleDeepLink(_ url: URL) {
let urlString = url.deletingLastPathComponent().absoluteString

switch urlString {
case Constants.groupBaseUrl:
type = .group(groupId: url.lastPathComponent)
default:
type = nil
}
}
}
4 changes: 4 additions & 0 deletions Data/Data/Utils/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ public struct Constants {
public static var shareAppURL: String {
return "https://apps.apple.com/in/app/splito/id6477442217"
}

public static var groupBaseUrl: String {
return "splito://groups/"
}
}
11 changes: 10 additions & 1 deletion Splito/Localization/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,12 @@
},
"Groups help you stay organized by tracking and splitting expenses for various activities." : {

},
"Hello %@, This is a reminder that I owe you %@ for expenses in the Splito group \\\"%@\\\". Please follow this link to review our activity: %@" : {
"extractionState" : "manual"
},
"Hello %@, This is a reminder that you owe me %@ for expenses in the Splito group \\\"%@\\\". Please follow this link to review our activity and settle up: %@" : {
"extractionState" : "manual"
},
"hour ago" : {
"extractionState" : "manual"
Expand Down Expand Up @@ -707,7 +713,7 @@
"extractionState" : "manual"
},
"Settle up" : {

"extractionState" : "manual"
},
"Settle up bills together!" : {
"extractionState" : "manual"
Expand Down Expand Up @@ -850,6 +856,9 @@
"This group has been deleted." : {
"extractionState" : "manual"
},
"This is a reminder that %@ owes %@ %@ for expenses in the Splito group \\\"%@\\\". Please follow this link to review your activity and settle up: %@" : {
"extractionState" : "manual"
},
"This month" : {
"extractionState" : "manual"
},
Expand Down
29 changes: 29 additions & 0 deletions Splito/Plist/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>CFBundleTypeName</string>
<key>CFBundleTypeRole</key>
<string>document</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>LSItemContentTypes</string>
</array>
</dict>
<dict>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
Expand All @@ -22,6 +41,16 @@
<string>$(REVERSE_CLIENT_ID)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>SplitoApp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>splito</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(app_version_code)</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,13 @@ private struct GroupBalanceItemView: View {
.frame(maxWidth: .infinity, alignment: .leading)

if memberBalance.totalOwedAmount != 0 {
ScrollToTopButton(icon: "chevron.down", iconColor: primaryText,
bgColor: container2Color, showWithAnimation: true, size: (10, 7),
isFirstGroupCell: memberBalance.isExpanded) {
toggleExpandBtn(memberBalance.id)
}
ScrollToTopButton(
icon: "chevron.down", iconColor: primaryText, bgColor: container2Color,
showWithAnimation: true, size: (10, 7), isFirstGroupCell: memberBalance.isExpanded,
onClick: {
toggleExpandBtn(memberBalance.id)
}
)
.onAppear {
if memberBalance.isExpanded {
toggleExpandBtn(memberBalance.id)
Expand All @@ -145,12 +147,17 @@ private struct GroupBalanceItemView: View {
private struct GroupBalanceItemMemberView: View {
let SUB_IMAGE_HEIGHT: CGFloat = 24

@Environment(\.dismiss) var dismiss

@Inject private var preference: SplitoPreference

let id: String
let balances: [String: Double]
let viewModel: GroupBalancesViewModel

@State private var showShareReminderSheet = false
@State private var reminderText: String?

var body: some View {
HStack(alignment: .top, spacing: 0) {
HSpacer(32)
Expand Down Expand Up @@ -179,24 +186,72 @@ private struct GroupBalanceItemMemberView: View {
.foregroundStyle(disableText)
}

HStack(alignment: .center, spacing: 16) {
HSpacer(SUB_IMAGE_HEIGHT)

Button {
viewModel.handleSettleUpTap(payerId: hasDue ? id : memberId, receiverId: hasDue ? memberId : id, amount: amount)
} label: {
Text("Settle up")
.font(.caption1())
.foregroundStyle(primaryText)
.padding(.vertical, 8)
.padding(.horizontal, 24)
.background(container2Color)
.cornerRadius(30)
RemindAndSettleBtnView(
handleRemindTap: {
let oweText = ((hasDue ? id : memberId) == preference.user?.id) ? "owe" :
(memberId == preference.user?.id || id == preference.user?.id) ? "owes" : ""
reminderText = generateReminderText(owedMemberName: owedMemberName, owesText: oweText,
amount: amount, owesMemberName: owesMemberName)
showShareReminderSheet = true
}, handleSettleUpTap: {
viewModel.handleSettleUpTap(payerId: hasDue ? id : memberId,
receiverId: hasDue ? memberId : id, amount: amount)
}
}
)
}
}
}
}
.sheet(isPresented: $showShareReminderSheet) {
if let reminderText {
ShareSheetView(activityItems: [reminderText]) { isCompleted in
if isCompleted {
showShareReminderSheet = false
}
}
}
}
}

private func generateReminderText(owedMemberName: String, owesText: String, amount: Double, owesMemberName: String) -> String {
let formattedAmount = amount.formattedCurrency
let groupName = viewModel.group?.name ?? ""
let deepLink = "\(Constants.groupBaseUrl)\(viewModel.groupId)"

if owesText == "owe" {
return "Hello \(owesMemberName), This is a reminder that I owe you \(formattedAmount) for expenses in the Splito group \"\(groupName)\". Please follow this link to review our activity: \(deepLink)"
} else if owesText == "owes" {
return "Hello \(owedMemberName), This is a reminder that you owe me \(formattedAmount) for expenses in the Splito group \"\(groupName)\". Please follow this link to review our activity and settle up: \(deepLink)"
} else {
return "This is a reminder that \(owedMemberName) owes \(owesMemberName) \(formattedAmount) for expenses in the Splito group \"\(groupName)\". Please follow this link to review your activity and settle up: \(deepLink)"
}
}
}

private struct RemindAndSettleBtnView: View {
let SUB_IMAGE_HEIGHT: CGFloat = 24

let handleRemindTap: () -> Void
let handleSettleUpTap: () -> Void

var body: some View {
HStack(alignment: .center, spacing: 16) {
HSpacer(SUB_IMAGE_HEIGHT)

balancesButton(title: "Remind", onClick: handleRemindTap)
balancesButton(title: "Settle up", onClick: handleSettleUpTap)
}
}

func balancesButton(title: String, onClick: @escaping () -> Void) -> some View {
Button(action: onClick) {
Text(title.localized)
.font(.caption1())
.foregroundStyle(primaryText)
.padding(.vertical, 8)
.padding(.horizontal, 24)
.background(container2Color)
.cornerRadius(30)
}
}
}
23 changes: 23 additions & 0 deletions Splito/UI/Home/Groups/GroupListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class GroupListViewModel: BaseViewModel, ObservableObject {
@Inject private var preference: SplitoPreference
@Inject private var groupRepository: GroupRepository
@Inject private var userRepository: UserRepository
@Inject private var deepLinkManager: DeepLinkManager

@Published private(set) var currentViewState: ViewState = .loading
@Published private(set) var groupListState: GroupListState = .noGroup
Expand Down Expand Up @@ -67,6 +68,7 @@ class GroupListViewModel: BaseViewModel, ObservableObject {

fetchGroupsInitialData()
fetchLatestUser()
deepLinkObserver()
}

deinit {
Expand All @@ -80,6 +82,27 @@ class GroupListViewModel: BaseViewModel, ObservableObject {
}
}

func deepLinkObserver() {
deepLinkManager.$type.sink { [weak self] type in
if case .group(let groupId) = type {
self?.removeExistingGroupPathIfNeeded(for: groupId)
self?.router.push(.GroupHomeView(groupId: groupId))
}
}
.store(in: &cancelable)
}

private func removeExistingGroupPathIfNeeded(for groupId: String) {
if let index = router.paths.firstIndex(where: { path in
if case .GroupHomeView(let id) = path, id == groupId {
return true
}
return false
}) {
router.paths.remove(at: index)
}
}

// MARK: - Data Loading
private func fetchGroups(needToReload: Bool = false) async {
guard let userId = preference.user?.id, hasMoreGroups || needToReload else {
Expand Down
3 changes: 3 additions & 0 deletions Splito/UI/Home/HomeRouteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,8 @@ struct HomeRouteView: View {
viewModel.switchToActivityLog(activityId: activityId)
}
}
.onOpenURL { url in
viewModel.handleDeepLink(url: url)
}
}
}
Loading

0 comments on commit fac97c3

Please sign in to comment.