Skip to content

Commit

Permalink
Add reminder support
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-amisha-i committed Jan 1, 2025
1 parent 3a51c7b commit 81fd22c
Show file tree
Hide file tree
Showing 61 changed files with 1,129 additions and 540 deletions.
2 changes: 1 addition & 1 deletion .firebaserc
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
}
},
"dataconnectEmulatorConfig": {}
}
}
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
}
}
}
1 change: 0 additions & 1 deletion Data/Data/Extension/Notification+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public extension Notification.Name {
static let addTransaction = Notification.Name("addTransaction")
static let updateTransaction = Notification.Name("updateTransaction")
static let deleteTransaction = Notification.Name("deleteTransaction")
static let restoreTransaction = Notification.Name("restoreTransaction")

static let joinGroup = Notification.Name("joinGroup")

Expand Down
2 changes: 1 addition & 1 deletion Data/Data/Model/AppUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public struct AppUser: Identifiable, Codable, Hashable, Sendable {
}

enum CodingKeys: String, CodingKey {
case id = "id"
case id
case firstName = "first_name"
case lastName = "last_name"
case emailId = "email_id"
Expand Down
4 changes: 2 additions & 2 deletions Data/Data/Repository/ActivityLogRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public class ActivityLogRepository: ObservableObject {

@Inject private var store: ActivityLogStore

public func fetchLatestActivityLogs(userId: String) -> AsyncStream<[ActivityLog]?> {
store.fetchLatestActivityLogs(userId: userId)
public func streamLatestActivityLogs(userId: String) -> AsyncStream<[ActivityLog]?> {
store.streamLatestActivityLogs(userId: userId)
}

public func addActivityLog(userId: String, activity: ActivityLog) async throws {
Expand Down
4 changes: 4 additions & 0 deletions Data/Data/Repository/GroupRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ public class GroupRepository: ObservableObject {
try await store.fetchGroupBy(id: id)
}

public func streamLatestGroupDataBy(id: String) -> AsyncStream<Groups?> {
store.streamLatestGroupBy(id: id)
}

public func fetchGroupsBy(userId: String, limit: Int = 10, lastDocument: DocumentSnapshot? = nil) async throws -> (data: [Groups], lastDocument: DocumentSnapshot?) {
try await store.fetchGroupsBy(userId: userId, limit: limit, lastDocument: lastDocument)
}
Expand Down
8 changes: 6 additions & 2 deletions Data/Data/Repository/TransactionRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,14 @@ public class TransactionRepository: ObservableObject {
}

public func fetchTransactionsBy(groupId: String, limit: Int = 10, lastDocument: DocumentSnapshot? = nil) async throws -> (transactions: [Transactions], lastDocument: DocumentSnapshot?) {
return try await store.fetchTransactionsBy(groupId: groupId, limit: limit, lastDocument: lastDocument)
try await store.fetchTransactionsBy(groupId: groupId, limit: limit, lastDocument: lastDocument)
}

public func fetchTransactionBy(groupId: String, transactionId: String) async throws -> Transactions {
return try await store.fetchTransactionsBy(groupId: groupId, transactionId: transactionId)
try await store.fetchTransactionsBy(groupId: groupId, transactionId: transactionId)
}

public func getTransactionsCount(groupId: String) async throws -> Int {
try await store.getTransactionsCount(groupId: groupId)
}
}
26 changes: 12 additions & 14 deletions Data/Data/Repository/UserRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public class UserRepository: ObservableObject {
try await store.fetchUserBy(email: email)
}

public func fetchLatestUserBy(userID: String) -> AsyncStream<AppUser?> {
store.fetchLatestUserBy(id: userID)
public func streamLatestUserBy(userID: String) -> AsyncStream<AppUser?> {
store.streamLatestUserBy(id: userID)
}

private func uploadImage(imageData: Data, user: AppUser) async throws -> AppUser {
Expand Down Expand Up @@ -94,18 +94,16 @@ public class UserRepository: ObservableObject {
try await user.delete()
}

public func updateDeviceFcmToken(retryCount: Int = 3) {
Task {
guard let userId = preference.user?.id, let fcmToken = preference.fcmToken else { return }

do {
try await store.updateUserDeviceFcmToken(userId: userId, fcmToken: fcmToken)
LogI("AppDelegate: \(#function) Device fcm token updated successfully.")
} catch {
LogE("AppDelegate: \(#function) Failed to update device fcm token: \(error).")
if retryCount > 0 {
updateDeviceFcmToken(retryCount: retryCount - 1)
}
public func updateDeviceFcmToken(retryCount: Int = 3) async {
guard let userId = preference.user?.id, let fcmToken = preference.fcmToken else { return }

do {
try await store.updateUserDeviceFcmToken(userId: userId, fcmToken: fcmToken)
LogI("UserRepository: \(#function) Device fcm token updated successfully.")
} catch {
LogE("UserRepository: \(#function) Failed to update device fcm token: \(error).")
if retryCount > 0 {
await updateDeviceFcmToken(retryCount: retryCount - 1)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Data/Data/Store/ActivityLogStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ActivityLogStore: ObservableObject {
.collection(SUB_COLLECTION_NAME)
}

func fetchLatestActivityLogs(userId: String) -> AsyncStream<[ActivityLog]?> {
func streamLatestActivityLogs(userId: String) -> AsyncStream<[ActivityLog]?> {
AsyncStream { continuation in
let query = activityReference(userId: userId)
.order(by: "recorded_on", descending: true)
Expand Down
31 changes: 31 additions & 0 deletions Data/Data/Store/GroupStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@ class GroupStore: ObservableObject {
}
}

func streamLatestGroupBy(id: String) -> AsyncStream<Groups?> {
AsyncStream { continuation in
let listener = groupReference.document(id).addSnapshotListener { snapshot, error in
if let error {
LogE("GroupStore: \(#function) Error fetching document: \(error).")
continuation.finish()
return
}

guard let snapshot else {
LogE("GroupStore: \(#function) Snapshot is nil for requested Group.")
continuation.yield(nil)
return
}

do {
let group = try snapshot.data(as: Groups.self)
continuation.yield(group)
} catch {
LogE("GroupStore: \(#function) Error decoding Group data: \(error).")
continuation.finish()
}
}

// Clean up: Remove listener when the stream is cancelled
continuation.onTermination = { _ in
listener.remove()
}
}
}

func fetchGroupsBy(userId: String, limit: Int, lastDocument: DocumentSnapshot?) async throws -> (data: [Groups], lastDocument: DocumentSnapshot?) {
var query = groupReference
.whereField("is_active", isEqualTo: true)
Expand Down
11 changes: 10 additions & 1 deletion Data/Data/Store/TransactionStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ public class TransactionStore: ObservableObject {
}

func fetchTransactionsBy(groupId: String, transactionId: String) async throws -> Transactions {
return try await transactionReference(groupId: groupId).document(transactionId).getDocument(as: Transactions.self, source: .server)
try await transactionReference(groupId: groupId)
.document(transactionId)
.getDocument(as: Transactions.self, source: .server)
}

func getTransactionsCount(groupId: String) async throws -> Int {
let count = try await transactionReference(groupId: groupId)
.whereField("is_active", isEqualTo: true)
.count.getAggregation(source: .server).count
return Int(truncating: count)
}
}
4 changes: 2 additions & 2 deletions Data/Data/Store/UserStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class UserStore: ObservableObject {
}
}

func fetchLatestUserBy(id: String) -> AsyncStream<AppUser?> {
func streamLatestUserBy(id: String) -> AsyncStream<AppUser?> {
AsyncStream { continuation in
let listener = usersCollection.document(id).addSnapshotListener { snapshot, error in
if let error {
Expand All @@ -63,7 +63,7 @@ class UserStore: ObservableObject {

guard let snapshot else {
LogE("UserStore: \(#function) Snapshot is nil for requested user.")
continuation.finish()
continuation.yield(nil)
return
}

Expand Down
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/"
}
}
54 changes: 34 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@ Whether it's a group trip 🌍, shared household bills 🏠, or any other collec
## Download App
<a href="https://apps.apple.com/in/app/splito-split-enjoy-together/id6477442217"> <img src="./Screenshots/AppStore.png" width="200"></img> </a>

## Features🌟

Splito is currently in active development 🚧, with plans to incorporate additional features shortly.

## Features 🌟
- **Group Management:** Create and manage multiple expense groups 👥 for different purposes (e.g., games, trips, shared bills).
- **Expense Tracking:** Add expenses 💰 with details such as description, amount, payer, and date.
- **Expense Splitting:** Split expenses equally⚖️ or based on customizable ratios among group members.
- **Payment Settlement:** Easily settle 💳 the payments with other group members to clear outstanding balances.
- **Expense Tracking:** Add expenses 💰 with relevant details such as description, amount, payer, date, attachments, and notes.
- **Expense Splitting:** Split expenses fairly⚖️ among group members or use customizable ratios for precise cost-sharing.
- **Payment Settlement:** Simplify 💳 payment settlements to clear outstanding balances with group members.
- **Log History:** For complete transparency, Keep track of all past transactions, expenses, and activities within the group.
- **Data Restoration:** Recover accidentally deleted data 🔄 with the restoration feature, ensuring nothing important is lost.
- **Due Payments Reminders:** Get timely notifications 🔔 for unpaid balances to help you stay on top of your financial commitments.

### Upcoming Features 🚧

Splito is currently in active development, with exciting new features planned:

- **Expense Comments:** Add comments 🗨️ to individual expenses for additional context or notes.
- **Custom Filters for Groups:** Apply filters 🔍 to sort and view groups based on specific criteria.
- **Multiple Currency Support:** Handle expenses and payments with multiple currencies 🌍 for seamless international group usage.
- **Expense Categories:** Organize expenses into predefined categories 📂 such as food, travel, or utilities for better clarity.
- **Group Expense Report:** Generate detailed reports 📄 for groups, summarizing spending patterns and balances.
- **Friends List:** Add and manage a list of friends 👯‍♂️ to streamline group creation and expense tracking.
- **Spending Charts:** Visualize spending habits 📊 with intuitive charts for better financial insights.
- **Group Wallet Support:** Introduce a shared group wallet 💼 for easy contribution and expense deduction.
- **Public Contribution Poll:** Allow group members to vote 🗳️ on contributions for events or shared purchases.

<details>
<summary> How to Use Splito </summary>
Expand All @@ -46,48 +60,48 @@ Splito is currently in active development 🚧, with plans to incorporate additi
<tr>
<th width="32%"> Group List </th>
<th width="32%"> Group Expense List </th>
<th width="32%"> Invite Friends/Group Members </th>
<th width="32%"> Expense Add/Update </th>
</tr>
<tr>
<td> <img src="./Screenshots/GroupList.png" /> </td>
<td> <img src="./Screenshots/GroupHome.png"/> </td>
<td> <img src="./Screenshots/InviteMember.png"/> </td>
<td> <img src="./Screenshots/AddExpense.png"/> </td>
</tr>
</table>
<table>
<tr>
<th width="32%"> Expense Add/Edit </th>
<th width="32%"> Expense Split Option </th>
<th width="32%"> Expense Split Options </th>
<th width="32%"> Expense Detail </th>
<th width="32%"> Group Balance </th>
</tr>
<tr>
<td> <img src="./Screenshots/AddExpense.png"/> </td>
<td> <img src="./Screenshots/SplitOptions.png"/> </td>
<td> <img src="./Screenshots/ExpenseDetail.png"/> </td>
<td> <img src="./Screenshots/GroupBalance.png"/> </td>
</tr>
</table>
<table>
<tr>
<th width="32%"> Group Balance </th>
<th width="32%"> Group Summary </th>
<th width="32%"> Activity Log History </th>
<th width="32%"> Payment Add/Edit </th>
<th width="32%"> Payment Details </th>
</tr>
<tr>
<td> <img src="./Screenshots/GroupBalance.png"/> </td>
<td> <img src="./Screenshots/GroupSummary.png"/> </td>
<td> <img src="./Screenshots/ActivityLogs.png"/> </td>
<td> <img src="./Screenshots/GroupPayment.png"/> </td>
<td> <img src="./Screenshots/TransactionDetail.png"/> </td>
</tr>
</table>
<table>
<tr>
<th width="32%"> Payment List </th>
<th width="32%"> Payment Add/Edit </th>
<th width="32%"> Payment Detail </th>
<th width="32%"> Activity Log History </th>
<th> </th>
</tr>
<tr>
<td> <img src="./Screenshots/Transactions.png"/> </td>
<td> <img src="./Screenshots/GroupPayment.png"/> </td>
<td> <img src="./Screenshots/TransactionDetail.png"/> </td>
<td> <img src="./Screenshots/ActivityLogs.png"/> </td>
<td> </td>
</tr>
</table>

Expand Down
Binary file modified Screenshots/ActivityLogs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/AddExpense.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/ExpenseDetail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/GroupBalance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/GroupHome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/GroupList.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/GroupPayment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/GroupSummary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed Screenshots/InviteMember.png
Binary file not shown.
Binary file modified Screenshots/SplitOptions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/TransactionDetail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/Transactions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 81fd22c

Please sign in to comment.