From 2d15a9bed9c06bfa7879edf80d1d50429c3d7fc1 Mon Sep 17 00:00:00 2001 From: Amisha Date: Mon, 13 Jan 2025 12:21:54 +0530 Subject: [PATCH 1/6] Set default currency during group creation --- Data/Data.xcodeproj/project.pbxproj | 16 + Data/Data/Model/Currency/Currencies.json | 934 ++++++++++++++++++ Data/Data/Model/Currency/Currency.swift | 27 + Data/Data/Model/Groups.swift | 18 +- Data/Data/Model/Transaction.swift | 27 +- Splito.xcodeproj/project.pbxproj | 4 + Splito/Resource/Currencies.json | 934 ++++++++++++++++++ .../Create Group/CreateGroupViewModel.swift | 5 +- .../Payment/GroupPaymentViewModel.swift | 7 +- 9 files changed, 1954 insertions(+), 18 deletions(-) create mode 100644 Data/Data/Model/Currency/Currencies.json create mode 100644 Data/Data/Model/Currency/Currency.swift create mode 100644 Splito/Resource/Currencies.json diff --git a/Data/Data.xcodeproj/project.pbxproj b/Data/Data.xcodeproj/project.pbxproj index 767a5358..6edde833 100644 --- a/Data/Data.xcodeproj/project.pbxproj +++ b/Data/Data.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ D8613F682BFDECC4008BD53D /* Query+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8613F672BFDECC4008BD53D /* Query+Extension.swift */; }; D865F8AE2BD7CB0B0084BD36 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D865F8AD2BD7CB0B0084BD36 /* Array+Extension.swift */; }; D88721432B99F133009DC5BE /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88721422B99F133009DC5BE /* StorageManager.swift */; }; + D888EA772D314D180003284B /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888EA762D314D130003284B /* Currency.swift */; }; + D888EA792D34E6BF0003284B /* Currencies.json in Resources */ = {isa = PBXBuildFile; fileRef = D888EA782D34E6BF0003284B /* Currencies.json */; }; D8910E382BB6D1D300877CE0 /* ExpenseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8910E372BB6D1D300877CE0 /* ExpenseStore.swift */; }; D895F2282CA297B900C2E4EB /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D895F2272CA297B900C2E4EB /* NetworkManager.swift */; }; D89BC2802D1C3DB200CDE86B /* DateTimeHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89BC27F2D1C3DB200CDE86B /* DateTimeHelper.swift */; }; @@ -93,6 +95,8 @@ D8613F672BFDECC4008BD53D /* Query+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+Extension.swift"; sourceTree = ""; }; D865F8AD2BD7CB0B0084BD36 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; D88721422B99F133009DC5BE /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; + D888EA762D314D130003284B /* Currency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = ""; }; + D888EA782D34E6BF0003284B /* Currencies.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Currencies.json; sourceTree = ""; }; D8910E372BB6D1D300877CE0 /* ExpenseStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpenseStore.swift; sourceTree = ""; }; D895F2272CA297B900C2E4EB /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; D89BC27F2D1C3DB200CDE86B /* DateTimeHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeHelper.swift; sourceTree = ""; }; @@ -193,9 +197,19 @@ path = Repository; sourceTree = ""; }; + D888EA7A2D34E6C50003284B /* Currency */ = { + isa = PBXGroup; + children = ( + D888EA762D314D130003284B /* Currency.swift */, + D888EA782D34E6BF0003284B /* Currencies.json */, + ); + path = Currency; + sourceTree = ""; + }; D89DBE262B88801F00E5F1BD /* Model */ = { isa = PBXGroup; children = ( + D888EA7A2D34E6C50003284B /* Currency */, D89DBE472B8CBE4C00E5F1BD /* AppUser.swift */, D83B15042B9996C0004A5F4F /* Groups.swift */, D85E86DD2BAB0292002EDF76 /* Expense.swift */, @@ -444,6 +458,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D888EA792D34E6BF0003284B /* Currencies.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -539,6 +554,7 @@ D8AC25BB2B7F327A00CEAAD3 /* SplitoPreference.swift in Sources */, D8AC25C32B7F390B00CEAAD3 /* AppAssembly.swift in Sources */, 21D8D0832C0857F10061B365 /* Constants.swift in Sources */, + D888EA772D314D180003284B /* Currency.swift in Sources */, D8AC25C12B7F38D300CEAAD3 /* Injector.swift in Sources */, D8D14A542BA092F500F45FF2 /* ShareCodeRepository.swift in Sources */, D8A7CA7B2BA5B6AC0014EC67 /* ShareCodeStore.swift in Sources */, diff --git a/Data/Data/Model/Currency/Currencies.json b/Data/Data/Model/Currency/Currencies.json new file mode 100644 index 00000000..717e875d --- /dev/null +++ b/Data/Data/Model/Currency/Currencies.json @@ -0,0 +1,934 @@ +{ + "currencies": [ + { + "code": "AED", + "name": "United Arab Emirates Dirham", + "symbol": "د.إ", + "region": "AE" + }, + { + "code": "AFN", + "name": "Afghan Afghani", + "symbol": "؋", + "region": "AF" + }, + { + "code": "ALL", + "name": "Albanian Lek", + "symbol": "L", + "region": "AL" + }, + { + "code": "AMD", + "name": "Armenian Dram", + "symbol": "֏", + "region": "AM" + }, + { + "code": "ANG", + "name": "Netherlands Antillean Guilder", + "symbol": "ƒ", + "region": "CW" + }, + { + "code": "AOA", + "name": "Angolan Kwanza", + "symbol": "Kz", + "region": "AO" + }, + { + "code": "ARS", + "name": "Argentine Peso", + "symbol": "$", + "region": "AR" + }, + { + "code": "AUD", + "name": "Australian Dollar", + "symbol": "$", + "region": "AU" + }, + { + "code": "AWG", + "name": "Aruban Florin", + "symbol": "ƒ", + "region": "AW" + }, + { + "code": "AZN", + "name": "Azerbaijani Manat", + "symbol": "₼", + "region": "AZ" + }, + { + "code": "BAM", + "name": "Bosnia and Herzegovina Convertible Mark", + "symbol": "KM", + "region": "BA" + }, + { + "code": "BBD", + "name": "Barbadian Dollar", + "symbol": "$", + "region": "BB" + }, + { + "code": "BDT", + "name": "Bangladeshi Taka", + "symbol": "৳", + "region": "BD" + }, + { + "code": "BGN", + "name": "Bulgarian Lev", + "symbol": "лв", + "region": "BG" + }, + { + "code": "BHD", + "name": "Bahraini Dinar", + "symbol": ".د.ب", + "region": "BH" + }, + { + "code": "BIF", + "name": "Burundian Franc", + "symbol": "FBu", + "region": "BI" + }, + { + "code": "BMD", + "name": "Bermudian Dollar", + "symbol": "$", + "region": "BM" + }, + { + "code": "BND", + "name": "Brunei Dollar", + "symbol": "$", + "region": "BN" + }, + { + "code": "BOB", + "name": "Bolivian Boliviano", + "symbol": "Bs.", + "region": "BO" + }, + { + "code": "BRL", + "name": "Brazilian Real", + "symbol": "R$", + "region": "BR" + }, + { + "code": "BSD", + "name": "Bahamian Dollar", + "symbol": "$", + "region": "BS" + }, + { + "code": "BTN", + "name": "Bhutanese Ngultrum", + "symbol": "Nu.", + "region": "BT" + }, + { + "code": "BWP", + "name": "Botswana Pula", + "symbol": "P", + "region": "BW" + }, + { + "code": "BYN", + "name": "Belarusian Ruble", + "symbol": "Br", + "region": "BY" + }, + { + "code": "BZD", + "name": "Belize Dollar", + "symbol": "BZ$", + "region": "BZ" + }, + { + "code": "CAD", + "name": "Canadian Dollar", + "symbol": "$", + "region": "CA" + }, + { + "code": "CDF", + "name": "Congolese Franc", + "symbol": "FC", + "region": "CD" + }, + { + "code": "CHF", + "name": "Swiss Franc", + "symbol": "CHF", + "region": "CH" + }, + { + "code": "CLP", + "name": "Chilean Peso", + "symbol": "$", + "region": "CL" + }, + { + "code": "CNY", + "name": "Chinese Yuan", + "symbol": "¥", + "region": "CN" + }, + { + "code": "COP", + "name": "Colombian Peso", + "symbol": "$", + "region": "CO" + }, + { + "code": "CRC", + "name": "Costa Rican Colón", + "symbol": "₡", + "region": "CR" + }, + { + "code": "CUP", + "name": "Cuban Peso", + "symbol": "₱", + "region": "CU" + }, + { + "code": "CVE", + "name": "Cape Verdean Escudo", + "symbol": "$", + "region": "CV" + }, + { + "code": "CZK", + "name": "Czech Koruna", + "symbol": "Kč", + "region": "CZ" + }, + { + "code": "DJF", + "name": "Djiboutian Franc", + "symbol": "Fdj", + "region": "DJ" + }, + { + "code": "DKK", + "name": "Danish Krone", + "symbol": "kr", + "region": "DK" + }, + { + "code": "DOP", + "name": "Dominican Peso", + "symbol": "RD$", + "region": "DO" + }, + { + "code": "DZD", + "name": "Algerian Dinar", + "symbol": "د.ج", + "region": "DZ" + }, + { + "code": "EGP", + "name": "Egyptian Pound", + "symbol": "£", + "region": "EG" + }, + { + "code": "ERN", + "name": "Eritrean Nakfa", + "symbol": "Nfk", + "region": "ER" + }, + { + "code": "ETB", + "name": "Ethiopian Birr", + "symbol": "Br", + "region": "ET" + }, + { + "code": "EUR", + "name": "Euro", + "symbol": "€", + "region": "EU" + }, + { + "code": "FJD", + "name": "Fijian Dollar", + "symbol": "$", + "region": "FJ" + }, + { + "code": "FKP", + "name": "Falkland Islands Pound", + "symbol": "£", + "region": "FK" + }, + { + "code": "GBP", + "name": "British Pound", + "symbol": "£", + "region": "GB" + }, + { + "code": "GEL", + "name": "Georgian Lari", + "symbol": "₾", + "region": "GE" + }, + { + "code": "GHS", + "name": "Ghanaian Cedi", + "symbol": "₵", + "region": "GH" + }, + { + "code": "GIP", + "name": "Gibraltar Pound", + "symbol": "£", + "region": "GI" + }, + { + "code": "GMD", + "name": "Gambian Dalasi", + "symbol": "D", + "region": "GM" + }, + { + "code": "GNF", + "name": "Guinean Franc", + "symbol": "FG", + "region": "GN" + }, + { + "code": "GTQ", + "name": "Guatemalan Quetzal", + "symbol": "Q", + "region": "GT" + }, + { + "code": "GYD", + "name": "Guyanese Dollar", + "symbol": "$", + "region": "GY" + }, + { + "code": "HKD", + "name": "Hong Kong Dollar", + "symbol": "$", + "region": "HK" + }, + { + "code": "HNL", + "name": "Honduran Lempira", + "symbol": "L", + "region": "HN" + }, + { + "code": "HRK", + "name": "Croatian Kuna", + "symbol": "kn", + "region": "HR" + }, + { + "code": "HTG", + "name": "Haitian Gourde", + "symbol": "G", + "region": "HT" + }, + { + "code": "HUF", + "name": "Hungarian Forint", + "symbol": "Ft", + "region": "HU" + }, + { + "code": "IDR", + "name": "Indonesian Rupiah", + "symbol": "Rp", + "region": "ID" + }, + { + "code": "ILS", + "name": "Israeli New Shekel", + "symbol": "₪", + "region": "IL" + }, + { + "code": "INR", + "name": "Indian Rupee", + "symbol": "₹", + "region": "IN" + }, + { + "code": "IQD", + "name": "Iraqi Dinar", + "symbol": "ع.د", + "region": "IQ" + }, + { + "code": "IRR", + "name": "Iranian Rial", + "symbol": "﷼", + "region": "IR" + }, + { + "code": "ISK", + "name": "Icelandic Króna", + "symbol": "kr", + "region": "IS" + }, + { + "code": "JMD", + "name": "Jamaican Dollar", + "symbol": "J$", + "region": "JM" + }, + { + "code": "JOD", + "name": "Jordanian Dinar", + "symbol": "د.ا", + "region": "JO" + }, + { + "code": "JPY", + "name": "Japanese Yen", + "symbol": "¥", + "region": "JP" + }, + { + "code": "KES", + "name": "Kenyan Shilling", + "symbol": "KSh", + "region": "KE" + }, + { + "code": "KGS", + "name": "Kyrgyzstani Som", + "symbol": "с", + "region": "KG" + }, + { + "code": "KHR", + "name": "Cambodian Riel", + "symbol": "៛", + "region": "KH" + }, + { + "code": "KMF", + "name": "Comorian Franc", + "symbol": "CF", + "region": "KM" + }, + { + "code": "KPW", + "name": "North Korean Won", + "symbol": "₩", + "region": "KP" + }, + { + "code": "KRW", + "name": "South Korean Won", + "symbol": "₩", + "region": "KR" + }, + { + "code": "KWD", + "name": "Kuwaiti Dinar", + "symbol": "د.ك", + "region": "KW" + }, + { + "code": "KYD", + "name": "Cayman Islands Dollar", + "symbol": "$", + "region": "KY" + }, + { + "code": "KZT", + "name": "Kazakhstani Tenge", + "symbol": "₸", + "region": "KZ" + }, + { + "code": "LAK", + "name": "Lao Kip", + "symbol": "₭", + "region": "LA" + }, + { + "code": "LBP", + "name": "Lebanese Pound", + "symbol": "ل.ل", + "region": "LB" + }, + { + "code": "LKR", + "name": "Sri Lankan Rupee", + "symbol": "₨", + "region": "LK" + }, + { + "code": "LRD", + "name": "Liberian Dollar", + "symbol": "$", + "region": "LR" + }, + { + "code": "LSL", + "name": "Lesotho Loti", + "symbol": "L", + "region": "LS" + }, + { + "code": "LYD", + "name": "Libyan Dinar", + "symbol": "ل.د", + "region": "LY" + }, + { + "code": "MAD", + "name": "Moroccan Dirham", + "symbol": "د.م.", + "region": "MA" + }, + { + "code": "MDL", + "name": "Moldovan Leu", + "symbol": "L", + "region": "MD" + }, + { + "code": "MGA", + "name": "Malagasy Ariary", + "symbol": "Ar", + "region": "MG" + }, + { + "code": "MKD", + "name": "Macedonian Denar", + "symbol": "ден", + "region": "MK" + }, + { + "code": "MMK", + "name": "Myanmar Kyat", + "symbol": "Ks", + "region": "MM" + }, + { + "code": "MNT", + "name": "Mongolian Tögrög", + "symbol": "₮", + "region": "MN" + }, + { + "code": "MOP", + "name": "Macanese Pataca", + "symbol": "P", + "region": "M" + }, + { + "code": "MRU", + "name": "Mauritanian Ouguiya", + "symbol": "UM", + "region": "MR" + }, + { + "code": "MUR", + "name": "Mauritian Rupee", + "symbol": "₨", + "region": "MU" + }, + { + "code": "MVR", + "name": "Maldivian Rufiyaa", + "symbol": ".ރ", + "region": "MV" + }, + { + "code": "MWK", + "name": "Malawian Kwacha", + "symbol": "MK", + "region": "MW" + }, + { + "code": "MXN", + "name": "Mexican Peso", + "symbol": "$", + "region": "MX" + }, + { + "code": "MYR", + "name": "Malaysian Ringgit", + "symbol": "RM", + "region": "MY" + }, + { + "code": "MZN", + "name": "Mozambican Metical", + "symbol": "MT", + "region": "MZ" + }, + { + "code": "NAD", + "name": "Namibian Dollar", + "symbol": "$", + "region": "NA" + }, + { + "code": "NGN", + "name": "Nigerian Naira", + "symbol": "₦", + "region": "NG" + }, + { + "code": "NIO", + "name": "Nicaraguan Córdoba", + "symbol": "C$", + "region": "NI" + }, + { + "code": "NOK", + "name": "Norwegian Krone", + "symbol": "kr", + "region": "NO" + }, + { + "code": "NPR", + "name": "Nepalese Rupee", + "symbol": "₨", + "region": "NP" + }, + { + "code": "NZD", + "name": "New Zealand Dollar", + "symbol": "$", + "region": "NZ" + }, + { + "code": "OMR", + "name": "Omani Rial", + "symbol": "ر.ع.", + "region": "OM" + }, + { + "code": "PAB", + "name": "Panamanian Balboa", + "symbol": "B/.", + "region": "PA" + }, + { + "code": "PEN", + "name": "Peruvian Sol", + "symbol": "S/", + "region": "PE" + }, + { + "code": "PGK", + "name": "Papua New Guinean Kina", + "symbol": "K", + "region": "PG" + }, + { + "code": "PHP", + "name": "Philippine Peso", + "symbol": "₱", + "region": "PH" + }, + { + "code": "PKR", + "name": "Pakistani Rupee", + "symbol": "₨", + "region": "PK" + }, + { + "code": "PLN", + "name": "Polish Złoty", + "symbol": "zł", + "region": "PL" + }, + { + "code": "PYG", + "name": "Paraguayan Guaraní", + "symbol": "₲", + "region": "PY" + }, + { + "code": "QAR", + "name": "Qatari Riyal", + "symbol": "ر.ق", + "region": "QA" + }, + { + "code": "RON", + "name": "Romanian Leu", + "symbol": "lei", + "region": "RO" + }, + { + "code": "RSD", + "name": "Serbian Dinar", + "symbol": "дин.", + "region": "RS" + }, + { + "code": "RUB", + "name": "Russian Ruble", + "symbol": "₽", + "region": "RU" + }, + { + "code": "RWF", + "name": "Rwandan Franc", + "symbol": "FRw", + "region": "RW" + }, + { + "code": "SAR", + "name": "Saudi Riyal", + "symbol": "ر.س", + "region": "SA" + }, + { + "code": "SBD", + "name": "Solomon Islands Dollar", + "symbol": "$", + "region": "SB" + }, + { + "code": "SCR", + "name": "Seychellois Rupee", + "symbol": "₨", + "region": "SC" + }, + { + "code": "SDG", + "name": "Sudanese Pound", + "symbol": "ج.س.", + "region": "SD" + }, + { + "code": "SEK", + "name": "Swedish Krona", + "symbol": "kr", + "region": "SE" + }, + { + "code": "SGD", + "name": "Singapore Dollar", + "symbol": "$", + "region": "SG" + }, + { + "code": "SHP", + "name": "Saint Helena Pound", + "symbol": "£", + "region": "SH" + }, + { + "code": "SLL", + "name": "Sierra Leonean Leone", + "symbol": "Le", + "region": "SL" + }, + { + "code": "SOS", + "name": "Somali Shilling", + "symbol": "Sh", + "region": "SO" + }, + { + "code": "SRD", + "name": "Surinamese Dollar", + "symbol": "$", + "region": "SR" + }, + { + "code": "SSP", + "name": "South Sudanese Pound", + "symbol": "£", + "region": "SS" + }, + { + "code": "STN", + "name": "São Tomé and Príncipe Dobra", + "symbol": "Db", + "region": "ST" + }, + { + "code": "SYP", + "name": "Syrian Pound", + "symbol": "£", + "region": "SY" + }, + { + "code": "SZL", + "name": "Swazi Lilangeni", + "symbol": "L", + "region": "SZ" + }, + { + "code": "THB", + "name": "Thai Baht", + "symbol": "฿", + "region": "TH" + }, + { + "code": "TJS", + "name": "Tajikistani Somoni", + "symbol": "ЅМ", + "region": "TJ" + }, + { + "code": "TMT", + "name": "Turkmenistan Manat", + "symbol": "m", + "region": "TM" + }, + { + "code": "TND", + "name": "Tunisian Dinar", + "symbol": "د.ت", + "region": "TN" + }, + { + "code": "TOP", + "name": "Tongan Paʻanga", + "symbol": "T$", + "region": "TO" + }, + { + "code": "TRY", + "name": "Turkish Lira", + "symbol": "₺", + "region": "TR" + }, + { + "code": "TTD", + "name": "Trinidad and Tobago Dollar", + "symbol": "$", + "region": "TT" + }, + { + "code": "TWD", + "name": "New Taiwan Dollar", + "symbol": "NT$", + "region": "TW" + }, + { + "code": "TZS", + "name": "Tanzanian Shilling", + "symbol": "TSh", + "region": "TZ" + }, + { + "code": "UAH", + "name": "Ukrainian Hryvnia", + "symbol": "₴", + "region": "UA" + }, + { + "code": "UGX", + "name": "Ugandan Shilling", + "symbol": "USh", + "region": "UG" + }, + { + "code": "USD", + "name": "United States Dollar", + "symbol": "$", + "region": "US" + }, + { + "code": "UYU", + "name": "Uruguayan Peso", + "symbol": "$", + "region": "UY" + }, + { + "code": "UZS", + "name": "Uzbekistani Som", + "symbol": "so'm", + "region": "UZ" + }, + { + "code": "VES", + "name": "Venezuelan Bolívar Soberano", + "symbol": "Bs.", + "region": "VE" + }, + { + "code": "VND", + "name": "Vietnamese Đồng", + "symbol": "₫", + "region": "VN" + }, + { + "code": "VUV", + "name": "Vanuatu Vatu", + "symbol": "VT", + "region": "VU" + }, + { + "code": "WST", + "name": "Samoan Tālā", + "symbol": "T", + "region": "WS" + }, + { + "code": "XAF", + "name": "Central African CFA Franc", + "symbol": "FCFA", + "region": "CM" + }, + { + "code": "XCD", + "name": "East Caribbean Dollar", + "symbol": "$", + "region": "AG" + }, + { + "code": "XOF", + "name": "West African CFA Franc", + "symbol": "CFA", + "region": "BJ" + }, + { + "code": "XPF", + "name": "CFP Franc", + "symbol": "₣", + "region": "PF" + }, + { + "code": "YER", + "name": "Yemeni Rial", + "symbol": "﷼", + "region": "YE" + }, + { + "code": "ZAR", + "name": "South African Rand", + "symbol": "R", + "region": "ZA" + }, + { + "code": "ZMW", + "name": "Zambian Kwacha", + "symbol": "ZK", + "region": "ZM" + }, + { + "code": "ZWL", + "name": "Zimbabwean Dollar", + "symbol": "$", + "region": "ZW" + } + ] +} diff --git a/Data/Data/Model/Currency/Currency.swift b/Data/Data/Model/Currency/Currency.swift new file mode 100644 index 00000000..018b5daa --- /dev/null +++ b/Data/Data/Model/Currency/Currency.swift @@ -0,0 +1,27 @@ +// +// Currency.swift +// Data +// +// Created by Amisha Italiya on 10/01/25. +// + +import Foundation + +public struct Currency: Codable { + public let code: String + public let name: String + public let symbol: String + public let region: String + + private static func getCurrencies() -> [Currency] { + let allCurrencies = JSONUtils.readJSONFromFile(fileName: "Currencies", type: [Currency].self, bundle: .baseBundle) ?? [] + return allCurrencies + } + + public static func getCurrentLocalCurrency() -> Currency { + let allCurrencies = getCurrencies() + let currentLocal = Locale.current.region?.identifier + return allCurrencies.first(where: { $0.region == currentLocal }) ?? + (allCurrencies.first ?? Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN")) + } +} diff --git a/Data/Data/Model/Groups.swift b/Data/Data/Model/Groups.swift index 0122c29c..6ea8f6d6 100644 --- a/Data/Data/Model/Groups.swift +++ b/Data/Data/Model/Groups.swift @@ -85,6 +85,15 @@ public struct GroupMemberBalance: Codable { } } +// public struct GroupMemberBalance: Codable { +// public let id: String /// Member Id +// public var balanceByCurrency: [String: GroupCurrencyBalance] /// Currency wise member balance +// } +// public struct GroupCurrencyBalance: Codable { +// public var balance: Double +// public var totalSummary: [GroupTotalSummary] +// } + public struct GroupTotalSummary: Codable { public var year: Int public var month: Int @@ -95,6 +104,12 @@ public struct GroupTotalSummary: Codable { self.month = month self.summary = summary } + + enum CodingKeys: String, CodingKey { + case year + case month + case summary + } } public struct GroupMemberSummary: Codable { @@ -139,7 +154,8 @@ public struct GroupInformation { public let members: [AppUser] public let hasExpenses: Bool - public init(group: Groups, userBalance: Double, memberOweAmount: [String: Double], members: [AppUser], hasExpenses: Bool) { + public init(group: Groups, userBalance: Double, memberOweAmount: [String: Double], + members: [AppUser], hasExpenses: Bool) { self.group = group self.userBalance = userBalance self.memberOweAmount = memberOweAmount diff --git a/Data/Data/Model/Transaction.swift b/Data/Data/Model/Transaction.swift index f71f8233..69f64553 100644 --- a/Data/Data/Model/Transaction.swift +++ b/Data/Data/Model/Transaction.swift @@ -13,28 +13,30 @@ public struct Transactions: Codable, Hashable, Identifiable { public var payerId: String public var receiverId: String + public var date: Timestamp public let addedBy: String + public var amount: Double + public var currencyCode: String? = "INR" public var updatedBy: String? public var note: String? - public var imageUrl: String? public var reason: String? - public var amount: Double - public var date: Timestamp + public var imageUrl: String? public var updatedAt: Timestamp? public var isActive: Bool - public init(payerId: String, receiverId: String, addedBy: String, updatedBy: String? = nil, - note: String? = nil, imageUrl: String? = nil, reason: String? = nil, amount: Double, - date: Timestamp, updatedAt: Timestamp? = nil, isActive: Bool = true) { + public init(payerId: String, receiverId: String, date: Timestamp, addedBy: String, amount: Double, + currencyCode: String? = "INR", updatedBy: String? = nil, note: String? = nil, reason: String? = nil, + imageUrl: String? = nil, updatedAt: Timestamp? = nil, isActive: Bool = true) { self.payerId = payerId self.receiverId = receiverId + self.date = date self.addedBy = addedBy + self.amount = amount + self.currencyCode = currencyCode self.updatedBy = updatedBy self.note = note - self.imageUrl = imageUrl self.reason = reason - self.amount = amount - self.date = date + self.imageUrl = imageUrl self.updatedAt = updatedAt self.isActive = isActive } @@ -43,13 +45,14 @@ public struct Transactions: Codable, Hashable, Identifiable { case id case payerId = "payer_id" case receiverId = "receiver_id" + case date case addedBy = "added_by" + case amount + case currencyCode = "currency_code" case updatedBy = "updated_by" case note - case imageUrl = "image_url" case reason - case amount - case date + case imageUrl = "image_url" case updatedAt = "updated_at" case isActive = "is_active" } diff --git a/Splito.xcodeproj/project.pbxproj b/Splito.xcodeproj/project.pbxproj index 5f6ad658..64552131 100644 --- a/Splito.xcodeproj/project.pbxproj +++ b/Splito.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ D88721452B9B2C78009DC5BE /* GroupListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88721442B9B2C78009DC5BE /* GroupListView.swift */; }; D88721472B9B2C97009DC5BE /* GroupListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88721462B9B2C97009DC5BE /* GroupListViewModel.swift */; }; D888EA722D2BB41F0003284B /* ExpensesSearchRouteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888EA712D2BB4180003284B /* ExpensesSearchRouteView.swift */; }; + D888EA752D3141460003284B /* Currencies.json in Resources */ = {isa = PBXBuildFile; fileRef = D888EA742D31413C0003284B /* Currencies.json */; }; D889F5B92B7A521F008C6A43 /* SplashView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D889F5B82B7A521F008C6A43 /* SplashView.storyboard */; }; D89684452B722D3400D5F721 /* SplitoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89684442B722D3400D5F721 /* SplitoApp.swift */; }; D89684492B722D3700D5F721 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D89684482B722D3700D5F721 /* Assets.xcassets */; }; @@ -226,6 +227,7 @@ D88721442B9B2C78009DC5BE /* GroupListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListView.swift; sourceTree = ""; }; D88721462B9B2C97009DC5BE /* GroupListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListViewModel.swift; sourceTree = ""; }; D888EA712D2BB4180003284B /* ExpensesSearchRouteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpensesSearchRouteView.swift; sourceTree = ""; }; + D888EA742D31413C0003284B /* Currencies.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Currencies.json; sourceTree = ""; }; D889F5B82B7A521F008C6A43 /* SplashView.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SplashView.storyboard; sourceTree = ""; }; D89684412B722D3400D5F721 /* Splito.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Splito.app; sourceTree = BUILT_PRODUCTS_DIR; }; D89684442B722D3400D5F721 /* SplitoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitoApp.swift; sourceTree = ""; }; @@ -652,6 +654,7 @@ children = ( D89684482B722D3700D5F721 /* Assets.xcassets */, D889F5B82B7A521F008C6A43 /* SplashView.storyboard */, + D888EA742D31413C0003284B /* Currencies.json */, ); path = Resource; sourceTree = ""; @@ -904,6 +907,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D888EA752D3141460003284B /* Currencies.json in Resources */, D896844C2B722D3700D5F721 /* Preview Assets.xcassets in Resources */, D89684492B722D3700D5F721 /* Assets.xcassets in Resources */, D889F5B92B7A521F008C6A43 /* SplashView.storyboard in Resources */, diff --git a/Splito/Resource/Currencies.json b/Splito/Resource/Currencies.json new file mode 100644 index 00000000..717e875d --- /dev/null +++ b/Splito/Resource/Currencies.json @@ -0,0 +1,934 @@ +{ + "currencies": [ + { + "code": "AED", + "name": "United Arab Emirates Dirham", + "symbol": "د.إ", + "region": "AE" + }, + { + "code": "AFN", + "name": "Afghan Afghani", + "symbol": "؋", + "region": "AF" + }, + { + "code": "ALL", + "name": "Albanian Lek", + "symbol": "L", + "region": "AL" + }, + { + "code": "AMD", + "name": "Armenian Dram", + "symbol": "֏", + "region": "AM" + }, + { + "code": "ANG", + "name": "Netherlands Antillean Guilder", + "symbol": "ƒ", + "region": "CW" + }, + { + "code": "AOA", + "name": "Angolan Kwanza", + "symbol": "Kz", + "region": "AO" + }, + { + "code": "ARS", + "name": "Argentine Peso", + "symbol": "$", + "region": "AR" + }, + { + "code": "AUD", + "name": "Australian Dollar", + "symbol": "$", + "region": "AU" + }, + { + "code": "AWG", + "name": "Aruban Florin", + "symbol": "ƒ", + "region": "AW" + }, + { + "code": "AZN", + "name": "Azerbaijani Manat", + "symbol": "₼", + "region": "AZ" + }, + { + "code": "BAM", + "name": "Bosnia and Herzegovina Convertible Mark", + "symbol": "KM", + "region": "BA" + }, + { + "code": "BBD", + "name": "Barbadian Dollar", + "symbol": "$", + "region": "BB" + }, + { + "code": "BDT", + "name": "Bangladeshi Taka", + "symbol": "৳", + "region": "BD" + }, + { + "code": "BGN", + "name": "Bulgarian Lev", + "symbol": "лв", + "region": "BG" + }, + { + "code": "BHD", + "name": "Bahraini Dinar", + "symbol": ".د.ب", + "region": "BH" + }, + { + "code": "BIF", + "name": "Burundian Franc", + "symbol": "FBu", + "region": "BI" + }, + { + "code": "BMD", + "name": "Bermudian Dollar", + "symbol": "$", + "region": "BM" + }, + { + "code": "BND", + "name": "Brunei Dollar", + "symbol": "$", + "region": "BN" + }, + { + "code": "BOB", + "name": "Bolivian Boliviano", + "symbol": "Bs.", + "region": "BO" + }, + { + "code": "BRL", + "name": "Brazilian Real", + "symbol": "R$", + "region": "BR" + }, + { + "code": "BSD", + "name": "Bahamian Dollar", + "symbol": "$", + "region": "BS" + }, + { + "code": "BTN", + "name": "Bhutanese Ngultrum", + "symbol": "Nu.", + "region": "BT" + }, + { + "code": "BWP", + "name": "Botswana Pula", + "symbol": "P", + "region": "BW" + }, + { + "code": "BYN", + "name": "Belarusian Ruble", + "symbol": "Br", + "region": "BY" + }, + { + "code": "BZD", + "name": "Belize Dollar", + "symbol": "BZ$", + "region": "BZ" + }, + { + "code": "CAD", + "name": "Canadian Dollar", + "symbol": "$", + "region": "CA" + }, + { + "code": "CDF", + "name": "Congolese Franc", + "symbol": "FC", + "region": "CD" + }, + { + "code": "CHF", + "name": "Swiss Franc", + "symbol": "CHF", + "region": "CH" + }, + { + "code": "CLP", + "name": "Chilean Peso", + "symbol": "$", + "region": "CL" + }, + { + "code": "CNY", + "name": "Chinese Yuan", + "symbol": "¥", + "region": "CN" + }, + { + "code": "COP", + "name": "Colombian Peso", + "symbol": "$", + "region": "CO" + }, + { + "code": "CRC", + "name": "Costa Rican Colón", + "symbol": "₡", + "region": "CR" + }, + { + "code": "CUP", + "name": "Cuban Peso", + "symbol": "₱", + "region": "CU" + }, + { + "code": "CVE", + "name": "Cape Verdean Escudo", + "symbol": "$", + "region": "CV" + }, + { + "code": "CZK", + "name": "Czech Koruna", + "symbol": "Kč", + "region": "CZ" + }, + { + "code": "DJF", + "name": "Djiboutian Franc", + "symbol": "Fdj", + "region": "DJ" + }, + { + "code": "DKK", + "name": "Danish Krone", + "symbol": "kr", + "region": "DK" + }, + { + "code": "DOP", + "name": "Dominican Peso", + "symbol": "RD$", + "region": "DO" + }, + { + "code": "DZD", + "name": "Algerian Dinar", + "symbol": "د.ج", + "region": "DZ" + }, + { + "code": "EGP", + "name": "Egyptian Pound", + "symbol": "£", + "region": "EG" + }, + { + "code": "ERN", + "name": "Eritrean Nakfa", + "symbol": "Nfk", + "region": "ER" + }, + { + "code": "ETB", + "name": "Ethiopian Birr", + "symbol": "Br", + "region": "ET" + }, + { + "code": "EUR", + "name": "Euro", + "symbol": "€", + "region": "EU" + }, + { + "code": "FJD", + "name": "Fijian Dollar", + "symbol": "$", + "region": "FJ" + }, + { + "code": "FKP", + "name": "Falkland Islands Pound", + "symbol": "£", + "region": "FK" + }, + { + "code": "GBP", + "name": "British Pound", + "symbol": "£", + "region": "GB" + }, + { + "code": "GEL", + "name": "Georgian Lari", + "symbol": "₾", + "region": "GE" + }, + { + "code": "GHS", + "name": "Ghanaian Cedi", + "symbol": "₵", + "region": "GH" + }, + { + "code": "GIP", + "name": "Gibraltar Pound", + "symbol": "£", + "region": "GI" + }, + { + "code": "GMD", + "name": "Gambian Dalasi", + "symbol": "D", + "region": "GM" + }, + { + "code": "GNF", + "name": "Guinean Franc", + "symbol": "FG", + "region": "GN" + }, + { + "code": "GTQ", + "name": "Guatemalan Quetzal", + "symbol": "Q", + "region": "GT" + }, + { + "code": "GYD", + "name": "Guyanese Dollar", + "symbol": "$", + "region": "GY" + }, + { + "code": "HKD", + "name": "Hong Kong Dollar", + "symbol": "$", + "region": "HK" + }, + { + "code": "HNL", + "name": "Honduran Lempira", + "symbol": "L", + "region": "HN" + }, + { + "code": "HRK", + "name": "Croatian Kuna", + "symbol": "kn", + "region": "HR" + }, + { + "code": "HTG", + "name": "Haitian Gourde", + "symbol": "G", + "region": "HT" + }, + { + "code": "HUF", + "name": "Hungarian Forint", + "symbol": "Ft", + "region": "HU" + }, + { + "code": "IDR", + "name": "Indonesian Rupiah", + "symbol": "Rp", + "region": "ID" + }, + { + "code": "ILS", + "name": "Israeli New Shekel", + "symbol": "₪", + "region": "IL" + }, + { + "code": "INR", + "name": "Indian Rupee", + "symbol": "₹", + "region": "IN" + }, + { + "code": "IQD", + "name": "Iraqi Dinar", + "symbol": "ع.د", + "region": "IQ" + }, + { + "code": "IRR", + "name": "Iranian Rial", + "symbol": "﷼", + "region": "IR" + }, + { + "code": "ISK", + "name": "Icelandic Króna", + "symbol": "kr", + "region": "IS" + }, + { + "code": "JMD", + "name": "Jamaican Dollar", + "symbol": "J$", + "region": "JM" + }, + { + "code": "JOD", + "name": "Jordanian Dinar", + "symbol": "د.ا", + "region": "JO" + }, + { + "code": "JPY", + "name": "Japanese Yen", + "symbol": "¥", + "region": "JP" + }, + { + "code": "KES", + "name": "Kenyan Shilling", + "symbol": "KSh", + "region": "KE" + }, + { + "code": "KGS", + "name": "Kyrgyzstani Som", + "symbol": "с", + "region": "KG" + }, + { + "code": "KHR", + "name": "Cambodian Riel", + "symbol": "៛", + "region": "KH" + }, + { + "code": "KMF", + "name": "Comorian Franc", + "symbol": "CF", + "region": "KM" + }, + { + "code": "KPW", + "name": "North Korean Won", + "symbol": "₩", + "region": "KP" + }, + { + "code": "KRW", + "name": "South Korean Won", + "symbol": "₩", + "region": "KR" + }, + { + "code": "KWD", + "name": "Kuwaiti Dinar", + "symbol": "د.ك", + "region": "KW" + }, + { + "code": "KYD", + "name": "Cayman Islands Dollar", + "symbol": "$", + "region": "KY" + }, + { + "code": "KZT", + "name": "Kazakhstani Tenge", + "symbol": "₸", + "region": "KZ" + }, + { + "code": "LAK", + "name": "Lao Kip", + "symbol": "₭", + "region": "LA" + }, + { + "code": "LBP", + "name": "Lebanese Pound", + "symbol": "ل.ل", + "region": "LB" + }, + { + "code": "LKR", + "name": "Sri Lankan Rupee", + "symbol": "₨", + "region": "LK" + }, + { + "code": "LRD", + "name": "Liberian Dollar", + "symbol": "$", + "region": "LR" + }, + { + "code": "LSL", + "name": "Lesotho Loti", + "symbol": "L", + "region": "LS" + }, + { + "code": "LYD", + "name": "Libyan Dinar", + "symbol": "ل.د", + "region": "LY" + }, + { + "code": "MAD", + "name": "Moroccan Dirham", + "symbol": "د.م.", + "region": "MA" + }, + { + "code": "MDL", + "name": "Moldovan Leu", + "symbol": "L", + "region": "MD" + }, + { + "code": "MGA", + "name": "Malagasy Ariary", + "symbol": "Ar", + "region": "MG" + }, + { + "code": "MKD", + "name": "Macedonian Denar", + "symbol": "ден", + "region": "MK" + }, + { + "code": "MMK", + "name": "Myanmar Kyat", + "symbol": "Ks", + "region": "MM" + }, + { + "code": "MNT", + "name": "Mongolian Tögrög", + "symbol": "₮", + "region": "MN" + }, + { + "code": "MOP", + "name": "Macanese Pataca", + "symbol": "P", + "region": "M" + }, + { + "code": "MRU", + "name": "Mauritanian Ouguiya", + "symbol": "UM", + "region": "MR" + }, + { + "code": "MUR", + "name": "Mauritian Rupee", + "symbol": "₨", + "region": "MU" + }, + { + "code": "MVR", + "name": "Maldivian Rufiyaa", + "symbol": ".ރ", + "region": "MV" + }, + { + "code": "MWK", + "name": "Malawian Kwacha", + "symbol": "MK", + "region": "MW" + }, + { + "code": "MXN", + "name": "Mexican Peso", + "symbol": "$", + "region": "MX" + }, + { + "code": "MYR", + "name": "Malaysian Ringgit", + "symbol": "RM", + "region": "MY" + }, + { + "code": "MZN", + "name": "Mozambican Metical", + "symbol": "MT", + "region": "MZ" + }, + { + "code": "NAD", + "name": "Namibian Dollar", + "symbol": "$", + "region": "NA" + }, + { + "code": "NGN", + "name": "Nigerian Naira", + "symbol": "₦", + "region": "NG" + }, + { + "code": "NIO", + "name": "Nicaraguan Córdoba", + "symbol": "C$", + "region": "NI" + }, + { + "code": "NOK", + "name": "Norwegian Krone", + "symbol": "kr", + "region": "NO" + }, + { + "code": "NPR", + "name": "Nepalese Rupee", + "symbol": "₨", + "region": "NP" + }, + { + "code": "NZD", + "name": "New Zealand Dollar", + "symbol": "$", + "region": "NZ" + }, + { + "code": "OMR", + "name": "Omani Rial", + "symbol": "ر.ع.", + "region": "OM" + }, + { + "code": "PAB", + "name": "Panamanian Balboa", + "symbol": "B/.", + "region": "PA" + }, + { + "code": "PEN", + "name": "Peruvian Sol", + "symbol": "S/", + "region": "PE" + }, + { + "code": "PGK", + "name": "Papua New Guinean Kina", + "symbol": "K", + "region": "PG" + }, + { + "code": "PHP", + "name": "Philippine Peso", + "symbol": "₱", + "region": "PH" + }, + { + "code": "PKR", + "name": "Pakistani Rupee", + "symbol": "₨", + "region": "PK" + }, + { + "code": "PLN", + "name": "Polish Złoty", + "symbol": "zł", + "region": "PL" + }, + { + "code": "PYG", + "name": "Paraguayan Guaraní", + "symbol": "₲", + "region": "PY" + }, + { + "code": "QAR", + "name": "Qatari Riyal", + "symbol": "ر.ق", + "region": "QA" + }, + { + "code": "RON", + "name": "Romanian Leu", + "symbol": "lei", + "region": "RO" + }, + { + "code": "RSD", + "name": "Serbian Dinar", + "symbol": "дин.", + "region": "RS" + }, + { + "code": "RUB", + "name": "Russian Ruble", + "symbol": "₽", + "region": "RU" + }, + { + "code": "RWF", + "name": "Rwandan Franc", + "symbol": "FRw", + "region": "RW" + }, + { + "code": "SAR", + "name": "Saudi Riyal", + "symbol": "ر.س", + "region": "SA" + }, + { + "code": "SBD", + "name": "Solomon Islands Dollar", + "symbol": "$", + "region": "SB" + }, + { + "code": "SCR", + "name": "Seychellois Rupee", + "symbol": "₨", + "region": "SC" + }, + { + "code": "SDG", + "name": "Sudanese Pound", + "symbol": "ج.س.", + "region": "SD" + }, + { + "code": "SEK", + "name": "Swedish Krona", + "symbol": "kr", + "region": "SE" + }, + { + "code": "SGD", + "name": "Singapore Dollar", + "symbol": "$", + "region": "SG" + }, + { + "code": "SHP", + "name": "Saint Helena Pound", + "symbol": "£", + "region": "SH" + }, + { + "code": "SLL", + "name": "Sierra Leonean Leone", + "symbol": "Le", + "region": "SL" + }, + { + "code": "SOS", + "name": "Somali Shilling", + "symbol": "Sh", + "region": "SO" + }, + { + "code": "SRD", + "name": "Surinamese Dollar", + "symbol": "$", + "region": "SR" + }, + { + "code": "SSP", + "name": "South Sudanese Pound", + "symbol": "£", + "region": "SS" + }, + { + "code": "STN", + "name": "São Tomé and Príncipe Dobra", + "symbol": "Db", + "region": "ST" + }, + { + "code": "SYP", + "name": "Syrian Pound", + "symbol": "£", + "region": "SY" + }, + { + "code": "SZL", + "name": "Swazi Lilangeni", + "symbol": "L", + "region": "SZ" + }, + { + "code": "THB", + "name": "Thai Baht", + "symbol": "฿", + "region": "TH" + }, + { + "code": "TJS", + "name": "Tajikistani Somoni", + "symbol": "ЅМ", + "region": "TJ" + }, + { + "code": "TMT", + "name": "Turkmenistan Manat", + "symbol": "m", + "region": "TM" + }, + { + "code": "TND", + "name": "Tunisian Dinar", + "symbol": "د.ت", + "region": "TN" + }, + { + "code": "TOP", + "name": "Tongan Paʻanga", + "symbol": "T$", + "region": "TO" + }, + { + "code": "TRY", + "name": "Turkish Lira", + "symbol": "₺", + "region": "TR" + }, + { + "code": "TTD", + "name": "Trinidad and Tobago Dollar", + "symbol": "$", + "region": "TT" + }, + { + "code": "TWD", + "name": "New Taiwan Dollar", + "symbol": "NT$", + "region": "TW" + }, + { + "code": "TZS", + "name": "Tanzanian Shilling", + "symbol": "TSh", + "region": "TZ" + }, + { + "code": "UAH", + "name": "Ukrainian Hryvnia", + "symbol": "₴", + "region": "UA" + }, + { + "code": "UGX", + "name": "Ugandan Shilling", + "symbol": "USh", + "region": "UG" + }, + { + "code": "USD", + "name": "United States Dollar", + "symbol": "$", + "region": "US" + }, + { + "code": "UYU", + "name": "Uruguayan Peso", + "symbol": "$", + "region": "UY" + }, + { + "code": "UZS", + "name": "Uzbekistani Som", + "symbol": "so'm", + "region": "UZ" + }, + { + "code": "VES", + "name": "Venezuelan Bolívar Soberano", + "symbol": "Bs.", + "region": "VE" + }, + { + "code": "VND", + "name": "Vietnamese Đồng", + "symbol": "₫", + "region": "VN" + }, + { + "code": "VUV", + "name": "Vanuatu Vatu", + "symbol": "VT", + "region": "VU" + }, + { + "code": "WST", + "name": "Samoan Tālā", + "symbol": "T", + "region": "WS" + }, + { + "code": "XAF", + "name": "Central African CFA Franc", + "symbol": "FCFA", + "region": "CM" + }, + { + "code": "XCD", + "name": "East Caribbean Dollar", + "symbol": "$", + "region": "AG" + }, + { + "code": "XOF", + "name": "West African CFA Franc", + "symbol": "CFA", + "region": "BJ" + }, + { + "code": "XPF", + "name": "CFP Franc", + "symbol": "₣", + "region": "PF" + }, + { + "code": "YER", + "name": "Yemeni Rial", + "symbol": "﷼", + "region": "YE" + }, + { + "code": "ZAR", + "name": "South African Rand", + "symbol": "R", + "region": "ZA" + }, + { + "code": "ZMW", + "name": "Zambian Kwacha", + "symbol": "ZK", + "region": "ZM" + }, + { + "code": "ZWL", + "name": "Zimbabwean Dollar", + "symbol": "$", + "region": "ZW" + } + ] +} diff --git a/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift b/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift index 623b5838..69c8cf0f 100644 --- a/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift +++ b/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift @@ -101,9 +101,10 @@ class CreateGroupViewModel: BaseViewModel, ObservableObject { private func createGroup() async -> Bool { guard let userId = preference.user?.id else { return false } + let localCurrency = Currency.getCurrentLocalCurrency().code let memberBalance = GroupMemberBalance(id: userId, balance: 0, totalSummary: []) - let group = Groups(name: groupName.trimming(spaces: .leadingAndTrailing), - createdBy: userId, members: [userId], balances: [memberBalance]) + let group = Groups(name: groupName.trimming(spaces: .leadingAndTrailing), createdBy: userId, + members: [userId], balances: [memberBalance], currencyCode: localCurrency) do { showLoader = true diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift index 8c7253c0..a70920d7 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift @@ -236,6 +236,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { if let transaction { var newTransaction = transaction newTransaction.amount = amount + newTransaction.currencyCode = "INR" newTransaction.date = .init(date: paymentDate) newTransaction.payerId = payerId newTransaction.receiverId = receiverId @@ -246,9 +247,9 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { return await updateTransaction(transaction: newTransaction, oldTransaction: transaction) } else { - let transaction = Transactions(payerId: payerId, receiverId: receiverId, addedBy: userId, - note: paymentNote, reason: paymentReason, - amount: amount, date: .init(date: paymentDate)) + let transaction = Transactions(payerId: payerId, receiverId: receiverId, date: .init(date: paymentDate), + addedBy: userId, amount: amount, currencyCode: "INR", note: paymentNote, + reason: paymentReason) return await addTransaction(transaction: transaction) } } From 50641b4c5a02e1512e1e6f1b4a2085f25f9d917a Mon Sep 17 00:00:00 2001 From: Amisha Date: Mon, 13 Jan 2025 19:24:49 +0530 Subject: [PATCH 2/6] Add currency picker view --- Data/Data.xcodeproj/project.pbxproj | 4 + Data/Data/Extension/Bundle+Extension.swift | 16 + Data/Data/Model/Currency/Currencies.json | 1866 ++++++++--------- Data/Data/Model/Currency/Currency.swift | 24 +- Data/Data/Utils/JSONUtils.swift | 4 +- Splito.xcodeproj/project.pbxproj | 8 + Splito/Localization/Localizable.xcstrings | 11 +- Splito/UI/Home/Expense/AddAmountView.swift | 66 + Splito/UI/Home/Expense/AddExpenseView.swift | 8 +- .../UI/Home/Expense/AddExpenseViewModel.swift | 16 +- .../UI/Home/Expense/CurrencyPickerView.swift | 113 + .../Expense Detail/ExpenseDetailsView.swift | 2 +- .../Home/Expense/Note/AddNoteViewModel.swift | 2 +- .../Settle up/Payment/GroupPaymentView.swift | 59 +- .../GroupTransactionDetailView.swift | 7 +- 15 files changed, 1192 insertions(+), 1014 deletions(-) create mode 100644 Data/Data/Extension/Bundle+Extension.swift create mode 100644 Splito/UI/Home/Expense/AddAmountView.swift create mode 100644 Splito/UI/Home/Expense/CurrencyPickerView.swift diff --git a/Data/Data.xcodeproj/project.pbxproj b/Data/Data.xcodeproj/project.pbxproj index 6edde833..d2e263b4 100644 --- a/Data/Data.xcodeproj/project.pbxproj +++ b/Data/Data.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 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 */; }; + D81C61532D3542600044A1E6 /* Bundle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81C61522D3542600044A1E6 /* Bundle+Extension.swift */; }; D826C0DF2BDA95C900AAA449 /* Timestamp+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D826C0DE2BDA95C900AAA449 /* Timestamp+Extension.swift */; }; D83B15052B9996C0004A5F4F /* Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B15042B9996C0004A5F4F /* Groups.swift */; }; D83B15092B999789004A5F4F /* GroupRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B15082B999789004A5F4F /* GroupRepository.swift */; }; @@ -86,6 +87,7 @@ 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 = ""; }; 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 = ""; }; BED6C37AA3F8FD2A6350DE1C /* Pods-Data.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Data.debug.xcconfig"; path = "Target Support Files/Pods-Data/Pods-Data.debug.xcconfig"; sourceTree = ""; }; + D81C61522D3542600044A1E6 /* Bundle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extension.swift"; sourceTree = ""; }; D826C0DE2BDA95C900AAA449 /* Timestamp+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timestamp+Extension.swift"; sourceTree = ""; }; D83B15042B9996C0004A5F4F /* Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Groups.swift; sourceTree = ""; }; D83B15082B999789004A5F4F /* GroupRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupRepository.swift; sourceTree = ""; }; @@ -255,6 +257,7 @@ D865F8AD2BD7CB0B0084BD36 /* Array+Extension.swift */, D8A7CA7F2BA867F80014EC67 /* String+Extension.swift */, D89C934D2BC694C200FACD16 /* Double+Extension.swift */, + D81C61522D3542600044A1E6 /* Bundle+Extension.swift */, D826C0DE2BDA95C900AAA449 /* Timestamp+Extension.swift */, 213D0CC92C89DBC800D65C73 /* Notification+Extension.swift */, D8613F672BFDECC4008BD53D /* Query+Extension.swift */, @@ -549,6 +552,7 @@ D89DBE482B8CBE4C00E5F1BD /* AppUser.swift in Sources */, D8D14A522BA0917D00F45FF2 /* SharedCode.swift in Sources */, D89DBE462B8CBE0F00E5F1BD /* UserRepository.swift in Sources */, + D81C61532D3542600044A1E6 /* Bundle+Extension.swift in Sources */, D83B15052B9996C0004A5F4F /* Groups.swift in Sources */, D8CF5A252C199ADB0014E3AD /* TransactionRepository.swift in Sources */, D8AC25BB2B7F327A00CEAAD3 /* SplitoPreference.swift in Sources */, diff --git a/Data/Data/Extension/Bundle+Extension.swift b/Data/Data/Extension/Bundle+Extension.swift new file mode 100644 index 00000000..31e9d4b3 --- /dev/null +++ b/Data/Data/Extension/Bundle+Extension.swift @@ -0,0 +1,16 @@ +// +// Bundle+Extension.swift +// Data +// +// Created by Amisha Italiya on 21/02/24. +// + +import Foundation + +private class UIBundleFakeClass {} + +public extension Bundle { + static var dataBundle: Bundle { + return Bundle(for: UIBundleFakeClass.self) + } +} diff --git a/Data/Data/Model/Currency/Currencies.json b/Data/Data/Model/Currency/Currencies.json index 717e875d..824dc91c 100644 --- a/Data/Data/Model/Currency/Currencies.json +++ b/Data/Data/Model/Currency/Currencies.json @@ -1,934 +1,932 @@ -{ - "currencies": [ - { - "code": "AED", - "name": "United Arab Emirates Dirham", - "symbol": "د.إ", - "region": "AE" - }, - { - "code": "AFN", - "name": "Afghan Afghani", - "symbol": "؋", - "region": "AF" - }, - { - "code": "ALL", - "name": "Albanian Lek", - "symbol": "L", - "region": "AL" - }, - { - "code": "AMD", - "name": "Armenian Dram", - "symbol": "֏", - "region": "AM" - }, - { - "code": "ANG", - "name": "Netherlands Antillean Guilder", - "symbol": "ƒ", - "region": "CW" - }, - { - "code": "AOA", - "name": "Angolan Kwanza", - "symbol": "Kz", - "region": "AO" - }, - { - "code": "ARS", - "name": "Argentine Peso", - "symbol": "$", - "region": "AR" - }, - { - "code": "AUD", - "name": "Australian Dollar", - "symbol": "$", - "region": "AU" - }, - { - "code": "AWG", - "name": "Aruban Florin", - "symbol": "ƒ", - "region": "AW" - }, - { - "code": "AZN", - "name": "Azerbaijani Manat", - "symbol": "₼", - "region": "AZ" - }, - { - "code": "BAM", - "name": "Bosnia and Herzegovina Convertible Mark", - "symbol": "KM", - "region": "BA" - }, - { - "code": "BBD", - "name": "Barbadian Dollar", - "symbol": "$", - "region": "BB" - }, - { - "code": "BDT", - "name": "Bangladeshi Taka", - "symbol": "৳", - "region": "BD" - }, - { - "code": "BGN", - "name": "Bulgarian Lev", - "symbol": "лв", - "region": "BG" - }, - { - "code": "BHD", - "name": "Bahraini Dinar", - "symbol": ".د.ب", - "region": "BH" - }, - { - "code": "BIF", - "name": "Burundian Franc", - "symbol": "FBu", - "region": "BI" - }, - { - "code": "BMD", - "name": "Bermudian Dollar", - "symbol": "$", - "region": "BM" - }, - { - "code": "BND", - "name": "Brunei Dollar", - "symbol": "$", - "region": "BN" - }, - { - "code": "BOB", - "name": "Bolivian Boliviano", - "symbol": "Bs.", - "region": "BO" - }, - { - "code": "BRL", - "name": "Brazilian Real", - "symbol": "R$", - "region": "BR" - }, - { - "code": "BSD", - "name": "Bahamian Dollar", - "symbol": "$", - "region": "BS" - }, - { - "code": "BTN", - "name": "Bhutanese Ngultrum", - "symbol": "Nu.", - "region": "BT" - }, - { - "code": "BWP", - "name": "Botswana Pula", - "symbol": "P", - "region": "BW" - }, - { - "code": "BYN", - "name": "Belarusian Ruble", - "symbol": "Br", - "region": "BY" - }, - { - "code": "BZD", - "name": "Belize Dollar", - "symbol": "BZ$", - "region": "BZ" - }, - { - "code": "CAD", - "name": "Canadian Dollar", - "symbol": "$", - "region": "CA" - }, - { - "code": "CDF", - "name": "Congolese Franc", - "symbol": "FC", - "region": "CD" - }, - { - "code": "CHF", - "name": "Swiss Franc", - "symbol": "CHF", - "region": "CH" - }, - { - "code": "CLP", - "name": "Chilean Peso", - "symbol": "$", - "region": "CL" - }, - { - "code": "CNY", - "name": "Chinese Yuan", - "symbol": "¥", - "region": "CN" - }, - { - "code": "COP", - "name": "Colombian Peso", - "symbol": "$", - "region": "CO" - }, - { - "code": "CRC", - "name": "Costa Rican Colón", - "symbol": "₡", - "region": "CR" - }, - { - "code": "CUP", - "name": "Cuban Peso", - "symbol": "₱", - "region": "CU" - }, - { - "code": "CVE", - "name": "Cape Verdean Escudo", - "symbol": "$", - "region": "CV" - }, - { - "code": "CZK", - "name": "Czech Koruna", - "symbol": "Kč", - "region": "CZ" - }, - { - "code": "DJF", - "name": "Djiboutian Franc", - "symbol": "Fdj", - "region": "DJ" - }, - { - "code": "DKK", - "name": "Danish Krone", - "symbol": "kr", - "region": "DK" - }, - { - "code": "DOP", - "name": "Dominican Peso", - "symbol": "RD$", - "region": "DO" - }, - { - "code": "DZD", - "name": "Algerian Dinar", - "symbol": "د.ج", - "region": "DZ" - }, - { - "code": "EGP", - "name": "Egyptian Pound", - "symbol": "£", - "region": "EG" - }, - { - "code": "ERN", - "name": "Eritrean Nakfa", - "symbol": "Nfk", - "region": "ER" - }, - { - "code": "ETB", - "name": "Ethiopian Birr", - "symbol": "Br", - "region": "ET" - }, - { - "code": "EUR", - "name": "Euro", - "symbol": "€", - "region": "EU" - }, - { - "code": "FJD", - "name": "Fijian Dollar", - "symbol": "$", - "region": "FJ" - }, - { - "code": "FKP", - "name": "Falkland Islands Pound", - "symbol": "£", - "region": "FK" - }, - { - "code": "GBP", - "name": "British Pound", - "symbol": "£", - "region": "GB" - }, - { - "code": "GEL", - "name": "Georgian Lari", - "symbol": "₾", - "region": "GE" - }, - { - "code": "GHS", - "name": "Ghanaian Cedi", - "symbol": "₵", - "region": "GH" - }, - { - "code": "GIP", - "name": "Gibraltar Pound", - "symbol": "£", - "region": "GI" - }, - { - "code": "GMD", - "name": "Gambian Dalasi", - "symbol": "D", - "region": "GM" - }, - { - "code": "GNF", - "name": "Guinean Franc", - "symbol": "FG", - "region": "GN" - }, - { - "code": "GTQ", - "name": "Guatemalan Quetzal", - "symbol": "Q", - "region": "GT" - }, - { - "code": "GYD", - "name": "Guyanese Dollar", - "symbol": "$", - "region": "GY" - }, - { - "code": "HKD", - "name": "Hong Kong Dollar", - "symbol": "$", - "region": "HK" - }, - { - "code": "HNL", - "name": "Honduran Lempira", - "symbol": "L", - "region": "HN" - }, - { - "code": "HRK", - "name": "Croatian Kuna", - "symbol": "kn", - "region": "HR" - }, - { - "code": "HTG", - "name": "Haitian Gourde", - "symbol": "G", - "region": "HT" - }, - { - "code": "HUF", - "name": "Hungarian Forint", - "symbol": "Ft", - "region": "HU" - }, - { - "code": "IDR", - "name": "Indonesian Rupiah", - "symbol": "Rp", - "region": "ID" - }, - { - "code": "ILS", - "name": "Israeli New Shekel", - "symbol": "₪", - "region": "IL" - }, - { - "code": "INR", - "name": "Indian Rupee", - "symbol": "₹", - "region": "IN" - }, - { - "code": "IQD", - "name": "Iraqi Dinar", - "symbol": "ع.د", - "region": "IQ" - }, - { - "code": "IRR", - "name": "Iranian Rial", - "symbol": "﷼", - "region": "IR" - }, - { - "code": "ISK", - "name": "Icelandic Króna", - "symbol": "kr", - "region": "IS" - }, - { - "code": "JMD", - "name": "Jamaican Dollar", - "symbol": "J$", - "region": "JM" - }, - { - "code": "JOD", - "name": "Jordanian Dinar", - "symbol": "د.ا", - "region": "JO" - }, - { - "code": "JPY", - "name": "Japanese Yen", - "symbol": "¥", - "region": "JP" - }, - { - "code": "KES", - "name": "Kenyan Shilling", - "symbol": "KSh", - "region": "KE" - }, - { - "code": "KGS", - "name": "Kyrgyzstani Som", - "symbol": "с", - "region": "KG" - }, - { - "code": "KHR", - "name": "Cambodian Riel", - "symbol": "៛", - "region": "KH" - }, - { - "code": "KMF", - "name": "Comorian Franc", - "symbol": "CF", - "region": "KM" - }, - { - "code": "KPW", - "name": "North Korean Won", - "symbol": "₩", - "region": "KP" - }, - { - "code": "KRW", - "name": "South Korean Won", - "symbol": "₩", - "region": "KR" - }, - { - "code": "KWD", - "name": "Kuwaiti Dinar", - "symbol": "د.ك", - "region": "KW" - }, - { - "code": "KYD", - "name": "Cayman Islands Dollar", - "symbol": "$", - "region": "KY" - }, - { - "code": "KZT", - "name": "Kazakhstani Tenge", - "symbol": "₸", - "region": "KZ" - }, - { - "code": "LAK", - "name": "Lao Kip", - "symbol": "₭", - "region": "LA" - }, - { - "code": "LBP", - "name": "Lebanese Pound", - "symbol": "ل.ل", - "region": "LB" - }, - { - "code": "LKR", - "name": "Sri Lankan Rupee", - "symbol": "₨", - "region": "LK" - }, - { - "code": "LRD", - "name": "Liberian Dollar", - "symbol": "$", - "region": "LR" - }, - { - "code": "LSL", - "name": "Lesotho Loti", - "symbol": "L", - "region": "LS" - }, - { - "code": "LYD", - "name": "Libyan Dinar", - "symbol": "ل.د", - "region": "LY" - }, - { - "code": "MAD", - "name": "Moroccan Dirham", - "symbol": "د.م.", - "region": "MA" - }, - { - "code": "MDL", - "name": "Moldovan Leu", - "symbol": "L", - "region": "MD" - }, - { - "code": "MGA", - "name": "Malagasy Ariary", - "symbol": "Ar", - "region": "MG" - }, - { - "code": "MKD", - "name": "Macedonian Denar", - "symbol": "ден", - "region": "MK" - }, - { - "code": "MMK", - "name": "Myanmar Kyat", - "symbol": "Ks", - "region": "MM" - }, - { - "code": "MNT", - "name": "Mongolian Tögrög", - "symbol": "₮", - "region": "MN" - }, - { - "code": "MOP", - "name": "Macanese Pataca", - "symbol": "P", - "region": "M" - }, - { - "code": "MRU", - "name": "Mauritanian Ouguiya", - "symbol": "UM", - "region": "MR" - }, - { - "code": "MUR", - "name": "Mauritian Rupee", - "symbol": "₨", - "region": "MU" - }, - { - "code": "MVR", - "name": "Maldivian Rufiyaa", - "symbol": ".ރ", - "region": "MV" - }, - { - "code": "MWK", - "name": "Malawian Kwacha", - "symbol": "MK", - "region": "MW" - }, - { - "code": "MXN", - "name": "Mexican Peso", - "symbol": "$", - "region": "MX" - }, - { - "code": "MYR", - "name": "Malaysian Ringgit", - "symbol": "RM", - "region": "MY" - }, - { - "code": "MZN", - "name": "Mozambican Metical", - "symbol": "MT", - "region": "MZ" - }, - { - "code": "NAD", - "name": "Namibian Dollar", - "symbol": "$", - "region": "NA" - }, - { - "code": "NGN", - "name": "Nigerian Naira", - "symbol": "₦", - "region": "NG" - }, - { - "code": "NIO", - "name": "Nicaraguan Córdoba", - "symbol": "C$", - "region": "NI" - }, - { - "code": "NOK", - "name": "Norwegian Krone", - "symbol": "kr", - "region": "NO" - }, - { - "code": "NPR", - "name": "Nepalese Rupee", - "symbol": "₨", - "region": "NP" - }, - { - "code": "NZD", - "name": "New Zealand Dollar", - "symbol": "$", - "region": "NZ" - }, - { - "code": "OMR", - "name": "Omani Rial", - "symbol": "ر.ع.", - "region": "OM" - }, - { - "code": "PAB", - "name": "Panamanian Balboa", - "symbol": "B/.", - "region": "PA" - }, - { - "code": "PEN", - "name": "Peruvian Sol", - "symbol": "S/", - "region": "PE" - }, - { - "code": "PGK", - "name": "Papua New Guinean Kina", - "symbol": "K", - "region": "PG" - }, - { - "code": "PHP", - "name": "Philippine Peso", - "symbol": "₱", - "region": "PH" - }, - { - "code": "PKR", - "name": "Pakistani Rupee", - "symbol": "₨", - "region": "PK" - }, - { - "code": "PLN", - "name": "Polish Złoty", - "symbol": "zł", - "region": "PL" - }, - { - "code": "PYG", - "name": "Paraguayan Guaraní", - "symbol": "₲", - "region": "PY" - }, - { - "code": "QAR", - "name": "Qatari Riyal", - "symbol": "ر.ق", - "region": "QA" - }, - { - "code": "RON", - "name": "Romanian Leu", - "symbol": "lei", - "region": "RO" - }, - { - "code": "RSD", - "name": "Serbian Dinar", - "symbol": "дин.", - "region": "RS" - }, - { - "code": "RUB", - "name": "Russian Ruble", - "symbol": "₽", - "region": "RU" - }, - { - "code": "RWF", - "name": "Rwandan Franc", - "symbol": "FRw", - "region": "RW" - }, - { - "code": "SAR", - "name": "Saudi Riyal", - "symbol": "ر.س", - "region": "SA" - }, - { - "code": "SBD", - "name": "Solomon Islands Dollar", - "symbol": "$", - "region": "SB" - }, - { - "code": "SCR", - "name": "Seychellois Rupee", - "symbol": "₨", - "region": "SC" - }, - { - "code": "SDG", - "name": "Sudanese Pound", - "symbol": "ج.س.", - "region": "SD" - }, - { - "code": "SEK", - "name": "Swedish Krona", - "symbol": "kr", - "region": "SE" - }, - { - "code": "SGD", - "name": "Singapore Dollar", - "symbol": "$", - "region": "SG" - }, - { - "code": "SHP", - "name": "Saint Helena Pound", - "symbol": "£", - "region": "SH" - }, - { - "code": "SLL", - "name": "Sierra Leonean Leone", - "symbol": "Le", - "region": "SL" - }, - { - "code": "SOS", - "name": "Somali Shilling", - "symbol": "Sh", - "region": "SO" - }, - { - "code": "SRD", - "name": "Surinamese Dollar", - "symbol": "$", - "region": "SR" - }, - { - "code": "SSP", - "name": "South Sudanese Pound", - "symbol": "£", - "region": "SS" - }, - { - "code": "STN", - "name": "São Tomé and Príncipe Dobra", - "symbol": "Db", - "region": "ST" - }, - { - "code": "SYP", - "name": "Syrian Pound", - "symbol": "£", - "region": "SY" - }, - { - "code": "SZL", - "name": "Swazi Lilangeni", - "symbol": "L", - "region": "SZ" - }, - { - "code": "THB", - "name": "Thai Baht", - "symbol": "฿", - "region": "TH" - }, - { - "code": "TJS", - "name": "Tajikistani Somoni", - "symbol": "ЅМ", - "region": "TJ" - }, - { - "code": "TMT", - "name": "Turkmenistan Manat", - "symbol": "m", - "region": "TM" - }, - { - "code": "TND", - "name": "Tunisian Dinar", - "symbol": "د.ت", - "region": "TN" - }, - { - "code": "TOP", - "name": "Tongan Paʻanga", - "symbol": "T$", - "region": "TO" - }, - { - "code": "TRY", - "name": "Turkish Lira", - "symbol": "₺", - "region": "TR" - }, - { - "code": "TTD", - "name": "Trinidad and Tobago Dollar", - "symbol": "$", - "region": "TT" - }, - { - "code": "TWD", - "name": "New Taiwan Dollar", - "symbol": "NT$", - "region": "TW" - }, - { - "code": "TZS", - "name": "Tanzanian Shilling", - "symbol": "TSh", - "region": "TZ" - }, - { - "code": "UAH", - "name": "Ukrainian Hryvnia", - "symbol": "₴", - "region": "UA" - }, - { - "code": "UGX", - "name": "Ugandan Shilling", - "symbol": "USh", - "region": "UG" - }, - { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "region": "US" - }, - { - "code": "UYU", - "name": "Uruguayan Peso", - "symbol": "$", - "region": "UY" - }, - { - "code": "UZS", - "name": "Uzbekistani Som", - "symbol": "so'm", - "region": "UZ" - }, - { - "code": "VES", - "name": "Venezuelan Bolívar Soberano", - "symbol": "Bs.", - "region": "VE" - }, - { - "code": "VND", - "name": "Vietnamese Đồng", - "symbol": "₫", - "region": "VN" - }, - { - "code": "VUV", - "name": "Vanuatu Vatu", - "symbol": "VT", - "region": "VU" - }, - { - "code": "WST", - "name": "Samoan Tālā", - "symbol": "T", - "region": "WS" - }, - { - "code": "XAF", - "name": "Central African CFA Franc", - "symbol": "FCFA", - "region": "CM" - }, - { - "code": "XCD", - "name": "East Caribbean Dollar", - "symbol": "$", - "region": "AG" - }, - { - "code": "XOF", - "name": "West African CFA Franc", - "symbol": "CFA", - "region": "BJ" - }, - { - "code": "XPF", - "name": "CFP Franc", - "symbol": "₣", - "region": "PF" - }, - { - "code": "YER", - "name": "Yemeni Rial", - "symbol": "﷼", - "region": "YE" - }, - { - "code": "ZAR", - "name": "South African Rand", - "symbol": "R", - "region": "ZA" - }, - { - "code": "ZMW", - "name": "Zambian Kwacha", - "symbol": "ZK", - "region": "ZM" - }, - { - "code": "ZWL", - "name": "Zimbabwean Dollar", - "symbol": "$", - "region": "ZW" - } - ] -} +[ + { + "code": "AED", + "name": "United Arab Emirates Dirham", + "symbol": "د.إ", + "region": "AE" + }, + { + "code": "AFN", + "name": "Afghan Afghani", + "symbol": "؋", + "region": "AF" + }, + { + "code": "ALL", + "name": "Albanian Lek", + "symbol": "L", + "region": "AL" + }, + { + "code": "AMD", + "name": "Armenian Dram", + "symbol": "֏", + "region": "AM" + }, + { + "code": "ANG", + "name": "Netherlands Antillean Guilder", + "symbol": "ƒ", + "region": "CW" + }, + { + "code": "AOA", + "name": "Angolan Kwanza", + "symbol": "Kz", + "region": "AO" + }, + { + "code": "ARS", + "name": "Argentine Peso", + "symbol": "$", + "region": "AR" + }, + { + "code": "AUD", + "name": "Australian Dollar", + "symbol": "$", + "region": "AU" + }, + { + "code": "AWG", + "name": "Aruban Florin", + "symbol": "ƒ", + "region": "AW" + }, + { + "code": "AZN", + "name": "Azerbaijani Manat", + "symbol": "₼", + "region": "AZ" + }, + { + "code": "BAM", + "name": "Bosnia and Herzegovina Convertible Mark", + "symbol": "KM", + "region": "BA" + }, + { + "code": "BBD", + "name": "Barbadian Dollar", + "symbol": "$", + "region": "BB" + }, + { + "code": "BDT", + "name": "Bangladeshi Taka", + "symbol": "৳", + "region": "BD" + }, + { + "code": "BGN", + "name": "Bulgarian Lev", + "symbol": "лв", + "region": "BG" + }, + { + "code": "BHD", + "name": "Bahraini Dinar", + "symbol": ".د.ب", + "region": "BH" + }, + { + "code": "BIF", + "name": "Burundian Franc", + "symbol": "FBu", + "region": "BI" + }, + { + "code": "BMD", + "name": "Bermudian Dollar", + "symbol": "$", + "region": "BM" + }, + { + "code": "BND", + "name": "Brunei Dollar", + "symbol": "$", + "region": "BN" + }, + { + "code": "BOB", + "name": "Bolivian Boliviano", + "symbol": "Bs.", + "region": "BO" + }, + { + "code": "BRL", + "name": "Brazilian Real", + "symbol": "R$", + "region": "BR" + }, + { + "code": "BSD", + "name": "Bahamian Dollar", + "symbol": "$", + "region": "BS" + }, + { + "code": "BTN", + "name": "Bhutanese Ngultrum", + "symbol": "Nu.", + "region": "BT" + }, + { + "code": "BWP", + "name": "Botswana Pula", + "symbol": "P", + "region": "BW" + }, + { + "code": "BYN", + "name": "Belarusian Ruble", + "symbol": "Br", + "region": "BY" + }, + { + "code": "BZD", + "name": "Belize Dollar", + "symbol": "BZ$", + "region": "BZ" + }, + { + "code": "CAD", + "name": "Canadian Dollar", + "symbol": "$", + "region": "CA" + }, + { + "code": "CDF", + "name": "Congolese Franc", + "symbol": "FC", + "region": "CD" + }, + { + "code": "CHF", + "name": "Swiss Franc", + "symbol": "CHF", + "region": "CH" + }, + { + "code": "CLP", + "name": "Chilean Peso", + "symbol": "$", + "region": "CL" + }, + { + "code": "CNY", + "name": "Chinese Yuan", + "symbol": "¥", + "region": "CN" + }, + { + "code": "COP", + "name": "Colombian Peso", + "symbol": "$", + "region": "CO" + }, + { + "code": "CRC", + "name": "Costa Rican Colón", + "symbol": "₡", + "region": "CR" + }, + { + "code": "CUP", + "name": "Cuban Peso", + "symbol": "₱", + "region": "CU" + }, + { + "code": "CVE", + "name": "Cape Verdean Escudo", + "symbol": "$", + "region": "CV" + }, + { + "code": "CZK", + "name": "Czech Koruna", + "symbol": "Kč", + "region": "CZ" + }, + { + "code": "DJF", + "name": "Djiboutian Franc", + "symbol": "Fdj", + "region": "DJ" + }, + { + "code": "DKK", + "name": "Danish Krone", + "symbol": "kr", + "region": "DK" + }, + { + "code": "DOP", + "name": "Dominican Peso", + "symbol": "RD$", + "region": "DO" + }, + { + "code": "DZD", + "name": "Algerian Dinar", + "symbol": "د.ج", + "region": "DZ" + }, + { + "code": "EGP", + "name": "Egyptian Pound", + "symbol": "£", + "region": "EG" + }, + { + "code": "ERN", + "name": "Eritrean Nakfa", + "symbol": "Nfk", + "region": "ER" + }, + { + "code": "ETB", + "name": "Ethiopian Birr", + "symbol": "Br", + "region": "ET" + }, + { + "code": "EUR", + "name": "Euro", + "symbol": "€", + "region": "EU" + }, + { + "code": "FJD", + "name": "Fijian Dollar", + "symbol": "$", + "region": "FJ" + }, + { + "code": "FKP", + "name": "Falkland Islands Pound", + "symbol": "£", + "region": "FK" + }, + { + "code": "GBP", + "name": "British Pound", + "symbol": "£", + "region": "GB" + }, + { + "code": "GEL", + "name": "Georgian Lari", + "symbol": "₾", + "region": "GE" + }, + { + "code": "GHS", + "name": "Ghanaian Cedi", + "symbol": "₵", + "region": "GH" + }, + { + "code": "GIP", + "name": "Gibraltar Pound", + "symbol": "£", + "region": "GI" + }, + { + "code": "GMD", + "name": "Gambian Dalasi", + "symbol": "D", + "region": "GM" + }, + { + "code": "GNF", + "name": "Guinean Franc", + "symbol": "FG", + "region": "GN" + }, + { + "code": "GTQ", + "name": "Guatemalan Quetzal", + "symbol": "Q", + "region": "GT" + }, + { + "code": "GYD", + "name": "Guyanese Dollar", + "symbol": "$", + "region": "GY" + }, + { + "code": "HKD", + "name": "Hong Kong Dollar", + "symbol": "$", + "region": "HK" + }, + { + "code": "HNL", + "name": "Honduran Lempira", + "symbol": "L", + "region": "HN" + }, + { + "code": "HRK", + "name": "Croatian Kuna", + "symbol": "kn", + "region": "HR" + }, + { + "code": "HTG", + "name": "Haitian Gourde", + "symbol": "G", + "region": "HT" + }, + { + "code": "HUF", + "name": "Hungarian Forint", + "symbol": "Ft", + "region": "HU" + }, + { + "code": "IDR", + "name": "Indonesian Rupiah", + "symbol": "Rp", + "region": "ID" + }, + { + "code": "ILS", + "name": "Israeli New Shekel", + "symbol": "₪", + "region": "IL" + }, + { + "code": "INR", + "name": "Indian Rupee", + "symbol": "₹", + "region": "IN" + }, + { + "code": "IQD", + "name": "Iraqi Dinar", + "symbol": "ع.د", + "region": "IQ" + }, + { + "code": "IRR", + "name": "Iranian Rial", + "symbol": "﷼", + "region": "IR" + }, + { + "code": "ISK", + "name": "Icelandic Króna", + "symbol": "kr", + "region": "IS" + }, + { + "code": "JMD", + "name": "Jamaican Dollar", + "symbol": "J$", + "region": "JM" + }, + { + "code": "JOD", + "name": "Jordanian Dinar", + "symbol": "د.ا", + "region": "JO" + }, + { + "code": "JPY", + "name": "Japanese Yen", + "symbol": "¥", + "region": "JP" + }, + { + "code": "KES", + "name": "Kenyan Shilling", + "symbol": "KSh", + "region": "KE" + }, + { + "code": "KGS", + "name": "Kyrgyzstani Som", + "symbol": "с", + "region": "KG" + }, + { + "code": "KHR", + "name": "Cambodian Riel", + "symbol": "៛", + "region": "KH" + }, + { + "code": "KMF", + "name": "Comorian Franc", + "symbol": "CF", + "region": "KM" + }, + { + "code": "KPW", + "name": "North Korean Won", + "symbol": "₩", + "region": "KP" + }, + { + "code": "KRW", + "name": "South Korean Won", + "symbol": "₩", + "region": "KR" + }, + { + "code": "KWD", + "name": "Kuwaiti Dinar", + "symbol": "د.ك", + "region": "KW" + }, + { + "code": "KYD", + "name": "Cayman Islands Dollar", + "symbol": "$", + "region": "KY" + }, + { + "code": "KZT", + "name": "Kazakhstani Tenge", + "symbol": "₸", + "region": "KZ" + }, + { + "code": "LAK", + "name": "Lao Kip", + "symbol": "₭", + "region": "LA" + }, + { + "code": "LBP", + "name": "Lebanese Pound", + "symbol": "ل.ل", + "region": "LB" + }, + { + "code": "LKR", + "name": "Sri Lankan Rupee", + "symbol": "₨", + "region": "LK" + }, + { + "code": "LRD", + "name": "Liberian Dollar", + "symbol": "$", + "region": "LR" + }, + { + "code": "LSL", + "name": "Lesotho Loti", + "symbol": "L", + "region": "LS" + }, + { + "code": "LYD", + "name": "Libyan Dinar", + "symbol": "ل.د", + "region": "LY" + }, + { + "code": "MAD", + "name": "Moroccan Dirham", + "symbol": "د.م.", + "region": "MA" + }, + { + "code": "MDL", + "name": "Moldovan Leu", + "symbol": "L", + "region": "MD" + }, + { + "code": "MGA", + "name": "Malagasy Ariary", + "symbol": "Ar", + "region": "MG" + }, + { + "code": "MKD", + "name": "Macedonian Denar", + "symbol": "ден", + "region": "MK" + }, + { + "code": "MMK", + "name": "Myanmar Kyat", + "symbol": "Ks", + "region": "MM" + }, + { + "code": "MNT", + "name": "Mongolian Tögrög", + "symbol": "₮", + "region": "MN" + }, + { + "code": "MOP", + "name": "Macanese Pataca", + "symbol": "P", + "region": "M" + }, + { + "code": "MRU", + "name": "Mauritanian Ouguiya", + "symbol": "UM", + "region": "MR" + }, + { + "code": "MUR", + "name": "Mauritian Rupee", + "symbol": "₨", + "region": "MU" + }, + { + "code": "MVR", + "name": "Maldivian Rufiyaa", + "symbol": ".ރ", + "region": "MV" + }, + { + "code": "MWK", + "name": "Malawian Kwacha", + "symbol": "MK", + "region": "MW" + }, + { + "code": "MXN", + "name": "Mexican Peso", + "symbol": "$", + "region": "MX" + }, + { + "code": "MYR", + "name": "Malaysian Ringgit", + "symbol": "RM", + "region": "MY" + }, + { + "code": "MZN", + "name": "Mozambican Metical", + "symbol": "MT", + "region": "MZ" + }, + { + "code": "NAD", + "name": "Namibian Dollar", + "symbol": "$", + "region": "NA" + }, + { + "code": "NGN", + "name": "Nigerian Naira", + "symbol": "₦", + "region": "NG" + }, + { + "code": "NIO", + "name": "Nicaraguan Córdoba", + "symbol": "C$", + "region": "NI" + }, + { + "code": "NOK", + "name": "Norwegian Krone", + "symbol": "kr", + "region": "NO" + }, + { + "code": "NPR", + "name": "Nepalese Rupee", + "symbol": "₨", + "region": "NP" + }, + { + "code": "NZD", + "name": "New Zealand Dollar", + "symbol": "$", + "region": "NZ" + }, + { + "code": "OMR", + "name": "Omani Rial", + "symbol": "ر.ع.", + "region": "OM" + }, + { + "code": "PAB", + "name": "Panamanian Balboa", + "symbol": "B/.", + "region": "PA" + }, + { + "code": "PEN", + "name": "Peruvian Sol", + "symbol": "S/", + "region": "PE" + }, + { + "code": "PGK", + "name": "Papua New Guinean Kina", + "symbol": "K", + "region": "PG" + }, + { + "code": "PHP", + "name": "Philippine Peso", + "symbol": "₱", + "region": "PH" + }, + { + "code": "PKR", + "name": "Pakistani Rupee", + "symbol": "₨", + "region": "PK" + }, + { + "code": "PLN", + "name": "Polish Złoty", + "symbol": "zł", + "region": "PL" + }, + { + "code": "PYG", + "name": "Paraguayan Guaraní", + "symbol": "₲", + "region": "PY" + }, + { + "code": "QAR", + "name": "Qatari Riyal", + "symbol": "ر.ق", + "region": "QA" + }, + { + "code": "RON", + "name": "Romanian Leu", + "symbol": "lei", + "region": "RO" + }, + { + "code": "RSD", + "name": "Serbian Dinar", + "symbol": "дин.", + "region": "RS" + }, + { + "code": "RUB", + "name": "Russian Ruble", + "symbol": "₽", + "region": "RU" + }, + { + "code": "RWF", + "name": "Rwandan Franc", + "symbol": "FRw", + "region": "RW" + }, + { + "code": "SAR", + "name": "Saudi Riyal", + "symbol": "ر.س", + "region": "SA" + }, + { + "code": "SBD", + "name": "Solomon Islands Dollar", + "symbol": "$", + "region": "SB" + }, + { + "code": "SCR", + "name": "Seychellois Rupee", + "symbol": "₨", + "region": "SC" + }, + { + "code": "SDG", + "name": "Sudanese Pound", + "symbol": "ج.س.", + "region": "SD" + }, + { + "code": "SEK", + "name": "Swedish Krona", + "symbol": "kr", + "region": "SE" + }, + { + "code": "SGD", + "name": "Singapore Dollar", + "symbol": "$", + "region": "SG" + }, + { + "code": "SHP", + "name": "Saint Helena Pound", + "symbol": "£", + "region": "SH" + }, + { + "code": "SLL", + "name": "Sierra Leonean Leone", + "symbol": "Le", + "region": "SL" + }, + { + "code": "SOS", + "name": "Somali Shilling", + "symbol": "Sh", + "region": "SO" + }, + { + "code": "SRD", + "name": "Surinamese Dollar", + "symbol": "$", + "region": "SR" + }, + { + "code": "SSP", + "name": "South Sudanese Pound", + "symbol": "£", + "region": "SS" + }, + { + "code": "STN", + "name": "São Tomé and Príncipe Dobra", + "symbol": "Db", + "region": "ST" + }, + { + "code": "SYP", + "name": "Syrian Pound", + "symbol": "£", + "region": "SY" + }, + { + "code": "SZL", + "name": "Swazi Lilangeni", + "symbol": "L", + "region": "SZ" + }, + { + "code": "THB", + "name": "Thai Baht", + "symbol": "฿", + "region": "TH" + }, + { + "code": "TJS", + "name": "Tajikistani Somoni", + "symbol": "ЅМ", + "region": "TJ" + }, + { + "code": "TMT", + "name": "Turkmenistan Manat", + "symbol": "m", + "region": "TM" + }, + { + "code": "TND", + "name": "Tunisian Dinar", + "symbol": "د.ت", + "region": "TN" + }, + { + "code": "TOP", + "name": "Tongan Paʻanga", + "symbol": "T$", + "region": "TO" + }, + { + "code": "TRY", + "name": "Turkish Lira", + "symbol": "₺", + "region": "TR" + }, + { + "code": "TTD", + "name": "Trinidad and Tobago Dollar", + "symbol": "$", + "region": "TT" + }, + { + "code": "TWD", + "name": "New Taiwan Dollar", + "symbol": "NT$", + "region": "TW" + }, + { + "code": "TZS", + "name": "Tanzanian Shilling", + "symbol": "TSh", + "region": "TZ" + }, + { + "code": "UAH", + "name": "Ukrainian Hryvnia", + "symbol": "₴", + "region": "UA" + }, + { + "code": "UGX", + "name": "Ugandan Shilling", + "symbol": "USh", + "region": "UG" + }, + { + "code": "USD", + "name": "United States Dollar", + "symbol": "$", + "region": "US" + }, + { + "code": "UYU", + "name": "Uruguayan Peso", + "symbol": "$", + "region": "UY" + }, + { + "code": "UZS", + "name": "Uzbekistani Som", + "symbol": "so'm", + "region": "UZ" + }, + { + "code": "VES", + "name": "Venezuelan Bolívar Soberano", + "symbol": "Bs.", + "region": "VE" + }, + { + "code": "VND", + "name": "Vietnamese Đồng", + "symbol": "₫", + "region": "VN" + }, + { + "code": "VUV", + "name": "Vanuatu Vatu", + "symbol": "VT", + "region": "VU" + }, + { + "code": "WST", + "name": "Samoan Tālā", + "symbol": "T", + "region": "WS" + }, + { + "code": "XAF", + "name": "Central African CFA Franc", + "symbol": "FCFA", + "region": "CM" + }, + { + "code": "XCD", + "name": "East Caribbean Dollar", + "symbol": "$", + "region": "AG" + }, + { + "code": "XOF", + "name": "West African CFA Franc", + "symbol": "CFA", + "region": "BJ" + }, + { + "code": "XPF", + "name": "CFP Franc", + "symbol": "₣", + "region": "PF" + }, + { + "code": "YER", + "name": "Yemeni Rial", + "symbol": "﷼", + "region": "YE" + }, + { + "code": "ZAR", + "name": "South African Rand", + "symbol": "R", + "region": "ZA" + }, + { + "code": "ZMW", + "name": "Zambian Kwacha", + "symbol": "ZK", + "region": "ZM" + }, + { + "code": "ZWL", + "name": "Zimbabwean Dollar", + "symbol": "$", + "region": "ZW" + } +] diff --git a/Data/Data/Model/Currency/Currency.swift b/Data/Data/Model/Currency/Currency.swift index 018b5daa..b37d6911 100644 --- a/Data/Data/Model/Currency/Currency.swift +++ b/Data/Data/Model/Currency/Currency.swift @@ -7,21 +7,35 @@ import Foundation -public struct Currency: Codable { +public struct Currency: Decodable, Hashable { public let code: String public let name: String public let symbol: String public let region: String - private static func getCurrencies() -> [Currency] { - let allCurrencies = JSONUtils.readJSONFromFile(fileName: "Currencies", type: [Currency].self, bundle: .baseBundle) ?? [] + public init(code: String, name: String, symbol: String, region: String) { + self.code = code + self.name = name + self.symbol = symbol + self.region = region + } + + public static func getAllCurrencies() -> [Currency] { + let allCurrencies = JSONUtils.readJSONFromFile(fileName: "Currencies", type: [Currency].self, bundle: .dataBundle) ?? [] return allCurrencies } + public static func getCurrencyOfCode(_ code: String) -> Currency { + let allCurrencies = getAllCurrencies() + let currency = allCurrencies.first(where: { $0.code == code }) ?? Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN") + return currency + } + public static func getCurrentLocalCurrency() -> Currency { - let allCurrencies = getCurrencies() + let allCurrencies = getAllCurrencies() let currentLocal = Locale.current.region?.identifier - return allCurrencies.first(where: { $0.region == currentLocal }) ?? + let currency = allCurrencies.first(where: { $0.region == currentLocal }) ?? (allCurrencies.first ?? Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN")) + return currency } } diff --git a/Data/Data/Utils/JSONUtils.swift b/Data/Data/Utils/JSONUtils.swift index 8a383acc..1ea1d384 100644 --- a/Data/Data/Utils/JSONUtils.swift +++ b/Data/Data/Utils/JSONUtils.swift @@ -16,8 +16,10 @@ public struct JSONUtils { let jsonData = try decoder.decode(T.self, from: data) return jsonData } catch { - LogE("JSONUtils: \(#function) error - \(error).") + LogE("Error decoding JSON file: \(fileName), error: \(error)") } + } else { + LogE("JSON file not found: \(fileName)") } return nil } diff --git a/Splito.xcodeproj/project.pbxproj b/Splito.xcodeproj/project.pbxproj index 64552131..048b60dc 100644 --- a/Splito.xcodeproj/project.pbxproj +++ b/Splito.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ 21D8CF412CFF080F00463E4D /* EmailLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D8CF402CFF080F00463E4D /* EmailLoginViewModel.swift */; }; 21F27BDE2C36768D00196D62 /* ExpenseSplitOptionsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F27BDD2C36768D00196D62 /* ExpenseSplitOptionsTabView.swift */; }; 741540F86E36400CE27B1FAD /* Pods_SplitoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 701193A10871F36C3EDB356C /* Pods_SplitoTests.framework */; }; + D81C614F2D35141A0044A1E6 /* AddAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81C614E2D35141A0044A1E6 /* AddAmountView.swift */; }; + D81C61512D353EB70044A1E6 /* CurrencyPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81C61502D353EB50044A1E6 /* CurrencyPickerView.swift */; }; D826C0E22BDBD65600AAA449 /* GroupBalancesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D826C0E12BDBD65600AAA449 /* GroupBalancesView.swift */; }; D826C0E42BDBD66300AAA449 /* GroupBalancesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D826C0E32BDBD66300AAA449 /* GroupBalancesViewModel.swift */; }; D8302DA02B9F282F005ACA13 /* InviteMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8302D9F2B9F282F005ACA13 /* InviteMemberView.swift */; }; @@ -195,6 +197,8 @@ D8015C042B7A47CF0002886A /* Data.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Data.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D8015C082B7A47D80002886A /* UI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = UI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D815DFD62BEA26C200C0F862 /* Secrets.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = ""; }; + D81C614E2D35141A0044A1E6 /* AddAmountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAmountView.swift; sourceTree = ""; }; + D81C61502D353EB50044A1E6 /* CurrencyPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyPickerView.swift; sourceTree = ""; }; D826C0E12BDBD65600AAA449 /* GroupBalancesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBalancesView.swift; sourceTree = ""; }; D826C0E32BDBD66300AAA449 /* GroupBalancesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBalancesViewModel.swift; sourceTree = ""; }; D8302D9F2B9F282F005ACA13 /* InviteMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteMemberView.swift; sourceTree = ""; }; @@ -540,6 +544,8 @@ isa = PBXGroup; children = ( D85E86E62BB2E189002EDF76 /* ExpenseRouteView.swift */, + D81C614E2D35141A0044A1E6 /* AddAmountView.swift */, + D81C61502D353EB50044A1E6 /* CurrencyPickerView.swift */, D85E86DF2BAB06A3002EDF76 /* AddExpenseView.swift */, D85E86E22BAB06D9002EDF76 /* AddExpenseViewModel.swift */, D856C7302BCFD2080008A341 /* Expense Detail */, @@ -1140,6 +1146,7 @@ D8A7CA722BA486250014EC67 /* GroupSettingViewModel.swift in Sources */, D833445A2C0DD08400CD9F05 /* GroupSettleUpViewModel.swift in Sources */, D85E86EB2BB3FD59002EDF76 /* ChoosePayerViewModel.swift in Sources */, + D81C61512D353EB70044A1E6 /* CurrencyPickerView.swift in Sources */, 2163D3A92D265A7D004B4F20 /* FeedbackView.swift in Sources */, 21B1C09A2C1C59F10098B4FD /* GroupTransactionListView.swift in Sources */, D89DBE5B2B8DE97000E5F1BD /* GroupRouteView.swift in Sources */, @@ -1147,6 +1154,7 @@ D8E244BB2B9843A100C6C82A /* CreateGroupView.swift in Sources */, D89DBE422B8CA72700E5F1BD /* HomeRouteView.swift in Sources */, 21B1C09E2C1C5AA30098B4FD /* GroupExpenseListView.swift in Sources */, + D81C614F2D35141A0044A1E6 /* AddAmountView.swift in Sources */, D8D14A602BA2DCDB00F45FF2 /* UserProfileView.swift in Sources */, 21D8CF3F2CFF080300463E4D /* EmailLoginView.swift in Sources */, D88721452B9B2C78009DC5BE /* GroupListView.swift in Sources */, diff --git a/Splito/Localization/Localizable.xcstrings b/Splito/Localization/Localizable.xcstrings index 570ea53c..19483758 100644 --- a/Splito/Localization/Localizable.xcstrings +++ b/Splito/Localization/Localizable.xcstrings @@ -12,9 +12,6 @@ }, " %@ settled up" : { - }, - " ₹ 0.00" : { - }, " from the group" : { "extractionState" : "manual" @@ -128,6 +125,9 @@ }, "0" : { + }, + "0.00" : { + }, "1.23" : { "extractionState" : "manual" @@ -528,6 +528,9 @@ }, "no balance" : { + }, + "No country found for \"%@\"!" : { + }, "No email address" : { "extractionState" : "manual" @@ -1044,4 +1047,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Splito/UI/Home/Expense/AddAmountView.swift b/Splito/UI/Home/Expense/AddAmountView.swift new file mode 100644 index 00000000..34bfdefb --- /dev/null +++ b/Splito/UI/Home/Expense/AddAmountView.swift @@ -0,0 +1,66 @@ +// +// AddAmountView.swift +// BaseStyle +// +// Created by Amisha Italiya on 13/01/25. +// + +import SwiftUI +import BaseStyle + +struct AddAmountView: View { + + @Binding var amount: Double + @Binding var showCurrencyPicker: Bool + + @State private var amountString: String = "" + + var selectedCurrencyCode: String + var isAmountFocused: FocusState.Binding + + var body: some View { + HStack(spacing: 6) { + Text(selectedCurrencyCode) + .font(.Header3()) + .foregroundStyle(primaryText) + .padding(.vertical, 6) + .padding(.horizontal, 12) + .background(inversePrimaryText) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .onTouchGesture { + showCurrencyPicker = true + } + + TextField("0.00", text: $amountString) + .font(.Header1()) + .tint(primaryColor) + .focused(isAmountFocused) + .autocorrectionDisabled() + .keyboardType(.decimalPad) + .multilineTextAlignment(.center) + .foregroundStyle(amountString.isEmpty ? outlineColor : primaryText) + .onChange(of: amountString) { newValue in + formatAmount(newValue: newValue) + } + .onAppear { + amountString = amount == 0 ? "" : String(format: "%.2f", amount) + } + .frame(maxWidth: .infinity, alignment: .center) + } + .padding(24) + .overlay { + RoundedRectangle(cornerRadius: 16) + .stroke(outlineColor, lineWidth: 1) + } + } + + private func formatAmount(newValue: String) { + let numericInput = newValue.trimmingCharacters(in: .whitespaces) + if let value = Double(numericInput) { + amount = value + } else { + amount = 0 + } + amountString = numericInput.isEmpty ? "" : numericInput + } +} diff --git a/Splito/UI/Home/Expense/AddExpenseView.swift b/Splito/UI/Home/Expense/AddExpenseView.swift index 5470b417..3dda9d09 100644 --- a/Splito/UI/Home/Expense/AddExpenseView.swift +++ b/Splito/UI/Home/Expense/AddExpenseView.swift @@ -97,6 +97,11 @@ struct AddExpenseView: View { ImagePickerView(cropOption: .square, sourceType: !viewModel.sourceTypeIsCamera ? .photoLibrary : .camera, image: $viewModel.expenseImage, isPresented: $viewModel.showImagePicker) } + .sheet(isPresented: $viewModel.showCurrencyPicker) { + let currencies = Currency.getAllCurrencies() + CurrencyPickerView(currencies: currencies, selectedCurrency: $viewModel.selectedCurrency, + isPresented: $viewModel.showCurrencyPicker) + } .toolbar { ToolbarItem(placement: .topBarLeading) { CancelButton() @@ -135,7 +140,8 @@ private struct ExpenseInfoView: View { ExpenseDetailRow(name: $viewModel.expenseName, focusedField: focusedField, subtitle: "Description", field: .expenseName) - AmountRowView(amount: $viewModel.expenseAmount, isAmountFocused: $isAmountFocused, subtitle: "Amount") + AddAmountView(amount: $viewModel.expenseAmount, showCurrencyPicker: $viewModel.showCurrencyPicker, + selectedCurrencyCode: viewModel.selectedCurrency.symbol, isAmountFocused: $isAmountFocused) .focused(focusedField, equals: .amount) HStack(alignment: .top, spacing: 16) { diff --git a/Splito/UI/Home/Expense/AddExpenseViewModel.swift b/Splito/UI/Home/Expense/AddExpenseViewModel.swift index ee1764b8..fcbfd839 100644 --- a/Splito/UI/Home/Expense/AddExpenseViewModel.swift +++ b/Splito/UI/Home/Expense/AddExpenseViewModel.swift @@ -38,9 +38,11 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { @Published var showPayerSelection = false @Published var showImagePickerOptions = false @Published var showSplitTypeSelection = false + @Published var showCurrencyPicker = false @Published private(set) var showLoader = false @Published private(set) var sourceTypeIsCamera = false + @Published var selectedCurrency: Currency @Published var selectedGroup: Groups? @Published private(set) var expense: Expense? @Published private(set) var viewState: ViewState = .initial @@ -54,6 +56,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { self.router = router self.groupId = groupId self.expenseId = expenseId + self.selectedCurrency = Currency.getCurrentLocalCurrency() super.init() loadInitialData() } @@ -98,6 +101,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { selectedGroup = group groupMembers = group.members selectedMembers = group.members + selectedCurrency = Currency.getCurrencyOfCode(group.defaultCurrency ?? "INR") } LogD("AddExpenseViewModel: \(#function) Group fetched successfully.") } catch { @@ -305,12 +309,8 @@ extension AddExpenseViewModel { } 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 - } + 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 } } @@ -384,9 +384,7 @@ extension AddExpenseViewModel { let expenseInfo: [String: Any] = ["groupId": groupId, "expense": newExpense] NotificationCenter.default.post(name: .addExpense, object: nil, userInfo: expenseInfo) - if !group.hasExpenses { - selectedGroup?.hasExpenses = true - } + if !group.hasExpenses { selectedGroup?.hasExpenses = true } await updateGroupMemberBalance(expense: newExpense, updateType: .Add) showLoader = false diff --git a/Splito/UI/Home/Expense/CurrencyPickerView.swift b/Splito/UI/Home/Expense/CurrencyPickerView.swift new file mode 100644 index 00000000..06237778 --- /dev/null +++ b/Splito/UI/Home/Expense/CurrencyPickerView.swift @@ -0,0 +1,113 @@ +// +// CurrencyPickerView.swift +// Splito +// +// Created by Amisha Italiya on 13/01/25. +// + +import Data +import SwiftUI +import BaseStyle + +struct CurrencyPickerView: View { + + @Environment(\.dismiss) var dismiss + + var currencies: [Currency] + @Binding var selectedCurrency: Currency + @Binding var isPresented: Bool + + @State private var searchedCurrency: String = "" + @FocusState private var isFocused: Bool + + private var filteredCurrencies: [Currency] { + currencies.filter { currency in + searchedCurrency.isEmpty ? true : currency.name.lowercased().contains(searchedCurrency.lowercased()) || + currency.code.lowercased().contains(searchedCurrency.lowercased()) + } + } + + var body: some View { + VStack(spacing: 0) { + VStack(spacing: 16) { + NavigationBarTopView(title: "Select Currency", leadingButton: EmptyView(), + trailingButton: DismissButton(padding: (0, 0), foregroundColor: primaryText, + onDismissAction: { dismiss() }) + .fontWeight(.regular) + ) + + SearchBar(text: $searchedCurrency, isFocused: $isFocused, placeholder: "Search") + .padding(.vertical, -7) + .padding(.horizontal, 3) + .overlay(content: { + RoundedRectangle(cornerRadius: 12).stroke(outlineColor, lineWidth: 1) + }) + .focused($isFocused) + .onAppear { isFocused = true } + } + .padding(.bottom, 20) + .padding(.horizontal, 16) + + if filteredCurrencies.isEmpty { + CurrencyNotFoundView(searchedCurrency: searchedCurrency) + } else { + List(currencies, id: \.self) { currency in + CurrencyCellView(currency: currency) { + selectedCurrency = currency + isPresented = false + } + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets()) + } + .listStyle(.plain) + } + } + } +} + +private struct CurrencyNotFoundView: View { + + let searchedCurrency: String + + var body: some View { + VStack(spacing: 0) { + Spacer() + Text("No country found for \"\(searchedCurrency)\"!") + .font(.subTitle1()) + .foregroundStyle(disableText) + .padding(.bottom, 60) + Spacer() + } + .onTapGestureForced { + UIApplication.shared.endEditing() + } + } +} + +private struct CurrencyCellView: View { + + let currency: Currency + let onCellSelect: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 10) { + Text(currency.symbol) + .font(.Header4()) + .frame(width: 50) + + Text(currency.name) + .font(.body1(16)) + } + .padding(.horizontal, 10) + .foregroundStyle(primaryText) + + Divider() + .frame(height: 1) + .background(dividerColor) + .padding(.vertical, 14) + } + .contentShape(Rectangle()) + .onTouchGesture { onCellSelect() } + } +} diff --git a/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift b/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift index 4c789ecd..003d2fea 100644 --- a/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift +++ b/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift @@ -118,7 +118,7 @@ private struct ExpenseHeaderView: View { .font(.subTitle2()) .foregroundStyle(primaryText) - Text(viewModel.expense?.formattedAmount ?? "₹ 0") + Text(viewModel.expense?.formattedAmount ?? "0") .font(.Header3()) .foregroundStyle(primaryText) diff --git a/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift b/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift index 96882fbe..c16223e5 100644 --- a/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift +++ b/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift @@ -24,7 +24,7 @@ class AddNoteViewModel: BaseViewModel, ObservableObject { private let payment: Transactions? private let handleSaveNoteTap: ((_ note: String, _ reason: String?) -> Void)? - init(group: Groups?, expense: Expense? = nil, payment: Transactions? = nil, note: String, + init(group: Groups?, expense: Expense? = nil, note: String, payment: Transactions? = nil, paymentReason: String? = nil, handleSaveNoteTap: ((_ note: String, _ reason: String?) -> Void)? = nil) { self.group = group self.expense = expense diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift index ea5e81b4..0cdba5c0 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift @@ -61,7 +61,8 @@ struct GroupPaymentView: View { VSpacer(16) - AmountRowView(amount: $viewModel.amount, isAmountFocused: $isAmountFocused, subtitle: "Enter amount") +// AddAmountView(amount: $viewModel.amount, selectedCurrency: <#T##Currency#>, +// showCurrencyPicker: <#T##Bool#>, isAmountFocused: $isAmountFocused) Spacer(minLength: 40) } @@ -113,8 +114,8 @@ struct GroupPaymentView: View { } .sheet(isPresented: $viewModel.showAddNoteEditor) { NavigationStack { - AddNoteView(viewModel: AddNoteViewModel(group: viewModel.group, payment: viewModel.transaction, - note: viewModel.paymentNote, + AddNoteView(viewModel: AddNoteViewModel(group: viewModel.group, note: viewModel.paymentNote, + payment: viewModel.transaction, paymentReason: viewModel.paymentReason, handleSaveNoteTap: viewModel.handleNoteSaveBtnTap(note:reason:))) } @@ -122,58 +123,6 @@ struct GroupPaymentView: View { } } -struct AmountRowView: View { - - @Binding var amount: Double - var isAmountFocused: FocusState.Binding - - let subtitle: String - - @State private var amountString: String = "" - - var body: some View { - VStack(alignment: .center, spacing: 24) { - Text(subtitle.localized) - .font(.subTitle1()) - .foregroundStyle(primaryText) - .tracking(-0.2) - - TextField(" ₹ 0.00", text: $amountString) - .keyboardType(.decimalPad) - .font(.Header1()) - .tint(primaryColor) - .foregroundStyle(amountString.isEmpty ? outlineColor : primaryText) - .focused(isAmountFocused) - .multilineTextAlignment(.center) - .autocorrectionDisabled() - .onChange(of: amountString) { newValue in - formatAmount(newValue: newValue) - } - .onAppear { - amountString = amount == 0 ? "" : String(format: "₹ %.2f", amount) - } - } - .padding(16) - .overlay { - RoundedRectangle(cornerRadius: 16) - .stroke(outlineColor, lineWidth: 1) - } - } - - private func formatAmount(newValue: String) { - // Remove the "₹" symbol and whitespace to process the numeric value - let numericInput = newValue.replacingOccurrences(of: "₹", with: "").trimmingCharacters(in: .whitespaces) - if let value = Double(numericInput) { - amount = value - } else { - amount = 0 - } - - // Update amountString to include "₹" prefix - amountString = numericInput.isEmpty ? "" : "₹ " + numericInput - } -} - struct DatePickerView: View { @Binding var date: Date diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settlements/Transaction Detail/GroupTransactionDetailView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settlements/Transaction Detail/GroupTransactionDetailView.swift index c8d7ba3e..e6802228 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settlements/Transaction Detail/GroupTransactionDetailView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settlements/Transaction Detail/GroupTransactionDetailView.swift @@ -103,8 +103,9 @@ struct GroupTransactionDetailView: View { } .fullScreenCover(isPresented: $viewModel.showAddNoteEditor) { NavigationStack { - AddNoteView(viewModel: AddNoteViewModel(group: viewModel.group, payment: viewModel.transaction, - note: viewModel.paymentNote, paymentReason: viewModel.paymentReason)) + AddNoteView(viewModel: AddNoteViewModel(group: viewModel.group, note: viewModel.paymentNote, + payment: viewModel.transaction, + paymentReason: viewModel.paymentReason)) } } .navigationDestination(isPresented: $showImageDisplayView) { @@ -221,7 +222,7 @@ private struct TransactionSummaryView: View { .padding(.bottom, 8) } - Text(amount?.formattedCurrency ?? "₹ 0") + Text(amount?.formattedCurrency ?? "0") .font(.Header2()) .foregroundStyle(primaryText) From 8fcb9d0e8cd37798d991d96a995c31ab87733876 Mon Sep 17 00:00:00 2001 From: Amisha Date: Thu, 16 Jan 2025 12:40:39 +0530 Subject: [PATCH 3/6] Add currency selection for expense --- Splito/UI/Home/Expense/AddAmountView.swift | 10 +++------- .../UI/Home/Expense/AddExpenseViewModel.swift | 17 ++++++++--------- Splito/UI/Home/Expense/CurrencyPickerView.swift | 10 ++++++---- Splito/UI/Home/Groups/Group/GroupHomeView.swift | 1 - Splito/UI/Home/Groups/GroupListView.swift | 1 - .../Home/Groups/GroupListWithDetailView.swift | 1 - 6 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Splito/UI/Home/Expense/AddAmountView.swift b/Splito/UI/Home/Expense/AddAmountView.swift index 34bfdefb..d0b9b407 100644 --- a/Splito/UI/Home/Expense/AddAmountView.swift +++ b/Splito/UI/Home/Expense/AddAmountView.swift @@ -19,7 +19,7 @@ struct AddAmountView: View { var isAmountFocused: FocusState.Binding var body: some View { - HStack(spacing: 6) { + HStack(spacing: 8) { Text(selectedCurrencyCode) .font(.Header3()) .foregroundStyle(primaryText) @@ -38,7 +38,7 @@ struct AddAmountView: View { .autocorrectionDisabled() .keyboardType(.decimalPad) .multilineTextAlignment(.center) - .foregroundStyle(amountString.isEmpty ? outlineColor : primaryText) + .tint(amountString.isEmpty ? outlineColor : primaryText) .onChange(of: amountString) { newValue in formatAmount(newValue: newValue) } @@ -56,11 +56,7 @@ struct AddAmountView: View { private func formatAmount(newValue: String) { let numericInput = newValue.trimmingCharacters(in: .whitespaces) - if let value = Double(numericInput) { - amount = value - } else { - amount = 0 - } amountString = numericInput.isEmpty ? "" : numericInput + amount = Double(numericInput) ?? 0 } } diff --git a/Splito/UI/Home/Expense/AddExpenseViewModel.swift b/Splito/UI/Home/Expense/AddExpenseViewModel.swift index fcbfd839..1f1524c9 100644 --- a/Splito/UI/Home/Expense/AddExpenseViewModel.swift +++ b/Splito/UI/Home/Expense/AddExpenseViewModel.swift @@ -77,6 +77,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { viewState = .loading await fetchAndUpdateGroupData(groupId: groupId) selectedPayers = [userId: expenseAmount] + selectedCurrency = Currency.getCurrencyOfCode(selectedGroup?.defaultCurrency ?? "INR") viewState = .initial LogD("AddExpenseViewModel: \(#function) Group fetched successfully.") } @@ -101,7 +102,6 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { selectedGroup = group groupMembers = group.members selectedMembers = group.members - selectedCurrency = Currency.getCurrencyOfCode(group.defaultCurrency ?? "INR") } LogD("AddExpenseViewModel: \(#function) Group fetched successfully.") } catch { @@ -118,6 +118,8 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { selectedPayers = expense.paidBy expenseImageUrl = expense.imageUrl expenseNote = expense.note ?? "" + let defaultCurrency = selectedGroup?.defaultCurrency ?? "INR" + selectedCurrency = Currency.getCurrencyOfCode(expense.currencyCode ?? defaultCurrency) if let splitData = expense.splitData { self.splitData = splitData } @@ -241,10 +243,8 @@ extension AddExpenseViewModel { } self?.showAlert = false })) - case .authorized: - authorized() - default: - return + case .authorized: authorized() + default: return } } @@ -370,7 +370,8 @@ extension AddExpenseViewModel { guard let groupId else { return false } let splitTo = splitData.map { $0.key } let participants = Array(Set(splitTo + selectedPayers.keys)) - let expense = Expense(groupId: groupId, name: expenseName.trimming(spaces: .leadingAndTrailing), amount: expenseAmount, + let expense = Expense(groupId: groupId, name: expenseName.trimming(spaces: .leadingAndTrailing), + amount: expenseAmount, currencyCode: selectedCurrency.code, date: Timestamp(date: expenseDate), addedBy: userId, note: expenseNote, splitType: splitType, splitTo: splitTo, splitData: splitData, paidBy: selectedPayers, participants: participants) return await addExpense(group: group, expense: expense) @@ -400,7 +401,6 @@ extension AddExpenseViewModel { private func handleUpdateExpenseAction(userId: String, group: Groups, expense: Expense) async -> Bool { guard let groupId = group.id else { return false } - var newExpense = expense newExpense.groupId = groupId newExpense.name = expenseName.trimming(spaces: .leadingAndTrailing) @@ -409,6 +409,7 @@ extension AddExpenseViewModel { newExpense.updatedAt = Timestamp() newExpense.updatedBy = userId newExpense.note = expenseNote + newExpense.currencyCode = selectedCurrency.code if selectedPayers.count == 1, let payerId = selectedPayers.keys.first { newExpense.paidBy = [payerId: expenseAmount] @@ -422,13 +423,11 @@ extension AddExpenseViewModel { let participants = Array(Set(newExpense.splitTo + newExpense.paidBy.keys)) newExpense.participants = participants - return await updateExpense(group: group, expense: newExpense, oldExpense: expense) } private func updateExpense(group: Groups, expense: Expense, oldExpense: Expense) async -> Bool { guard validateMembersInGroup(group: group, expense: expense), let expenseId else { return false } - do { showLoader = true let updatedExpense = try await expenseRepository.updateExpenseWithImage(imageData: getImageData(), diff --git a/Splito/UI/Home/Expense/CurrencyPickerView.swift b/Splito/UI/Home/Expense/CurrencyPickerView.swift index 06237778..4c39c01a 100644 --- a/Splito/UI/Home/Expense/CurrencyPickerView.swift +++ b/Splito/UI/Home/Expense/CurrencyPickerView.swift @@ -21,9 +21,11 @@ struct CurrencyPickerView: View { @FocusState private var isFocused: Bool private var filteredCurrencies: [Currency] { - currencies.filter { currency in - searchedCurrency.isEmpty ? true : currency.name.lowercased().contains(searchedCurrency.lowercased()) || - currency.code.lowercased().contains(searchedCurrency.lowercased()) + guard !searchedCurrency.isEmpty else { return currencies } + return currencies.filter { currency in + currency.name.lowercased().contains(searchedCurrency.lowercased()) || + currency.code.lowercased().contains(searchedCurrency.lowercased()) || + currency.symbol.lowercased().contains(searchedCurrency.lowercased()) } } @@ -51,7 +53,7 @@ struct CurrencyPickerView: View { if filteredCurrencies.isEmpty { CurrencyNotFoundView(searchedCurrency: searchedCurrency) } else { - List(currencies, id: \.self) { currency in + List(filteredCurrencies, id: \.self) { currency in CurrencyCellView(currency: currency) { selectedCurrency = currency isPresented = false diff --git a/Splito/UI/Home/Groups/Group/GroupHomeView.swift b/Splito/UI/Home/Groups/Group/GroupHomeView.swift index 96b093ed..e067343d 100644 --- a/Splito/UI/Home/Groups/Group/GroupHomeView.swift +++ b/Splito/UI/Home/Groups/Group/GroupHomeView.swift @@ -219,7 +219,6 @@ struct EmptyStateView: View { .frame(minHeight: minHeight ?? geometry.size.height - 50, maxHeight: .infinity, alignment: .center) } .scrollIndicators(.hidden) - .scrollBounceBehavior(.basedOnSize) } } diff --git a/Splito/UI/Home/Groups/GroupListView.swift b/Splito/UI/Home/Groups/GroupListView.swift index fd64ac04..824f3575 100644 --- a/Splito/UI/Home/Groups/GroupListView.swift +++ b/Splito/UI/Home/Groups/GroupListView.swift @@ -224,7 +224,6 @@ private struct NoGroupsState: View { .frame(minHeight: geometry.size.height - 100, maxHeight: .infinity, alignment: .center) } .scrollIndicators(.hidden) - .scrollBounceBehavior(.basedOnSize) } } } diff --git a/Splito/UI/Home/Groups/GroupListWithDetailView.swift b/Splito/UI/Home/Groups/GroupListWithDetailView.swift index d299521d..ebd0268e 100644 --- a/Splito/UI/Home/Groups/GroupListWithDetailView.swift +++ b/Splito/UI/Home/Groups/GroupListWithDetailView.swift @@ -55,7 +55,6 @@ struct GroupListWithDetailView: View { perform: viewModel.manageScrollToTopBtnVisibility(offset:)) }) } - .scrollBounceBehavior(.basedOnSize) .refreshable { viewModel.fetchGroupsInitialData() } .overlay(alignment: .bottomTrailing) { if viewModel.showScrollToTopBtn { From 89b6ba7a6b1309155d6e37fb7f86b4c11078d294 Mon Sep 17 00:00:00 2001 From: Amisha Date: Thu, 16 Jan 2025 14:44:33 +0530 Subject: [PATCH 4/6] Add currency selection for payment --- Data/Data/Extension/Double+Extension.swift | 13 +++++ Data/Data/Model/Currency/Currency.swift | 2 +- Data/Data/Repository/UserRepository.swift | 10 ++++ Splito/UI/Home/Expense/AddAmountView.swift | 4 +- Splito/UI/Home/Expense/AddExpenseView.swift | 2 +- .../UI/Home/Expense/AddExpenseViewModel.swift | 4 +- .../Settle up/Payment/GroupPaymentView.swift | 9 +++- .../Payment/GroupPaymentViewModel.swift | 49 +++++++------------ 8 files changed, 53 insertions(+), 40 deletions(-) diff --git a/Data/Data/Extension/Double+Extension.swift b/Data/Data/Extension/Double+Extension.swift index 1d1ba6ca..938bec65 100644 --- a/Data/Data/Extension/Double+Extension.swift +++ b/Data/Data/Extension/Double+Extension.swift @@ -20,6 +20,19 @@ public extension Double { } } + func formattedCurrencyWithSign(_ sign: String) -> String { + let amount: String + let formatter = NumberFormatter() + formatter.locale = Locale.current + + if let formattedAmount = formatter.string(from: NSNumber(value: self)) { + amount = formattedAmount + } else { + amount = String(format: "%.2f", self.rounded()) // Fallback to a basic decimal format + } + return sign + " " + amount + } + var formattedCurrencyWithSign: String { let formatter = NumberFormatter() formatter.numberStyle = .currency diff --git a/Data/Data/Model/Currency/Currency.swift b/Data/Data/Model/Currency/Currency.swift index b37d6911..d4aeb270 100644 --- a/Data/Data/Model/Currency/Currency.swift +++ b/Data/Data/Model/Currency/Currency.swift @@ -25,7 +25,7 @@ public struct Currency: Decodable, Hashable { return allCurrencies } - public static func getCurrencyOfCode(_ code: String) -> Currency { + public static func getCurrencyFromCode(_ code: String) -> Currency { let allCurrencies = getAllCurrencies() let currency = allCurrencies.first(where: { $0.code == code }) ?? Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN") return currency diff --git a/Data/Data/Repository/UserRepository.swift b/Data/Data/Repository/UserRepository.swift index ed1cf05f..e28e325c 100644 --- a/Data/Data/Repository/UserRepository.swift +++ b/Data/Data/Repository/UserRepository.swift @@ -35,6 +35,16 @@ public class UserRepository: ObservableObject { try await store.fetchUserBy(id: userID) } + public func fetchUsersBy(userIds: [String]) async throws -> [AppUser] { + var users: [AppUser] = [] + for userId in userIds { + let user = try await fetchUserBy(userID: userId) + guard let user else { continue } + users.append(user) + } + return users.uniqued() + } + public func fetchUserBy(email: String) async throws -> AppUser? { try await store.fetchUserBy(email: email) } diff --git a/Splito/UI/Home/Expense/AddAmountView.swift b/Splito/UI/Home/Expense/AddAmountView.swift index d0b9b407..ba807ee3 100644 --- a/Splito/UI/Home/Expense/AddAmountView.swift +++ b/Splito/UI/Home/Expense/AddAmountView.swift @@ -15,12 +15,12 @@ struct AddAmountView: View { @State private var amountString: String = "" - var selectedCurrencyCode: String + var selectedCurrencySymbol: String var isAmountFocused: FocusState.Binding var body: some View { HStack(spacing: 8) { - Text(selectedCurrencyCode) + Text(selectedCurrencySymbol) .font(.Header3()) .foregroundStyle(primaryText) .padding(.vertical, 6) diff --git a/Splito/UI/Home/Expense/AddExpenseView.swift b/Splito/UI/Home/Expense/AddExpenseView.swift index 3dda9d09..96b51fdc 100644 --- a/Splito/UI/Home/Expense/AddExpenseView.swift +++ b/Splito/UI/Home/Expense/AddExpenseView.swift @@ -141,7 +141,7 @@ private struct ExpenseInfoView: View { subtitle: "Description", field: .expenseName) AddAmountView(amount: $viewModel.expenseAmount, showCurrencyPicker: $viewModel.showCurrencyPicker, - selectedCurrencyCode: viewModel.selectedCurrency.symbol, isAmountFocused: $isAmountFocused) + selectedCurrencySymbol: viewModel.selectedCurrency.symbol, isAmountFocused: $isAmountFocused) .focused(focusedField, equals: .amount) HStack(alignment: .top, spacing: 16) { diff --git a/Splito/UI/Home/Expense/AddExpenseViewModel.swift b/Splito/UI/Home/Expense/AddExpenseViewModel.swift index 1f1524c9..06b9154d 100644 --- a/Splito/UI/Home/Expense/AddExpenseViewModel.swift +++ b/Splito/UI/Home/Expense/AddExpenseViewModel.swift @@ -77,7 +77,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { viewState = .loading await fetchAndUpdateGroupData(groupId: groupId) selectedPayers = [userId: expenseAmount] - selectedCurrency = Currency.getCurrencyOfCode(selectedGroup?.defaultCurrency ?? "INR") + selectedCurrency = Currency.getCurrencyFromCode(selectedGroup?.defaultCurrency ?? "INR") viewState = .initial LogD("AddExpenseViewModel: \(#function) Group fetched successfully.") } @@ -119,7 +119,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { expenseImageUrl = expense.imageUrl expenseNote = expense.note ?? "" let defaultCurrency = selectedGroup?.defaultCurrency ?? "INR" - selectedCurrency = Currency.getCurrencyOfCode(expense.currencyCode ?? defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(expense.currencyCode ?? defaultCurrency) if let splitData = expense.splitData { self.splitData = splitData } diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift index 0cdba5c0..6104278e 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift @@ -61,8 +61,8 @@ struct GroupPaymentView: View { VSpacer(16) -// AddAmountView(amount: $viewModel.amount, selectedCurrency: <#T##Currency#>, -// showCurrencyPicker: <#T##Bool#>, isAmountFocused: $isAmountFocused) + AddAmountView(amount: $viewModel.amount, showCurrencyPicker: $viewModel.showCurrencyPicker, + selectedCurrencySymbol: viewModel.selectedCurrency.symbol, isAmountFocused: $isAmountFocused) Spacer(minLength: 40) } @@ -112,6 +112,11 @@ struct GroupPaymentView: View { ImagePickerView(cropOption: .square, sourceType: !viewModel.sourceTypeIsCamera ? .photoLibrary : .camera, image: $viewModel.paymentImage, isPresented: $viewModel.showImagePicker) } + .sheet(isPresented: $viewModel.showCurrencyPicker) { + let currencies = Currency.getAllCurrencies() + CurrencyPickerView(currencies: currencies, selectedCurrency: $viewModel.selectedCurrency, + isPresented: $viewModel.showCurrencyPicker) + } .sheet(isPresented: $viewModel.showAddNoteEditor) { NavigationStack { AddNoteView(viewModel: AddNoteViewModel(group: viewModel.group, note: viewModel.paymentNote, diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift index a70920d7..8cdc738c 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift @@ -22,7 +22,6 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { @Published var amount: Double = 0 @Published var paymentDate = Date() @Published var paymentImage: UIImage? - @Published var paymentNote: String = "" @Published var paymentReason: String = "" @Published private(set) var paymentImageUrl: String? @@ -30,9 +29,11 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { @Published var showImagePicker = false @Published var showAddNoteEditor = false @Published var showImagePickerOptions = false + @Published var showCurrencyPicker = false @Published private(set) var showLoader: Bool = false @Published private(set) var sourceTypeIsCamera = false + @Published var selectedCurrency: Currency @Published private(set) var payer: AppUser? @Published private(set) var receiver: AppUser? @Published private(set) var viewState: ViewState = .loading @@ -64,7 +65,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { self.payerId = payerId self.receiverId = receiverId self.transactionId = transactionId - + self.selectedCurrency = Currency.getCurrentLocalCurrency() super.init() fetchInitialViewData() @@ -74,8 +75,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { Task { [weak self] in await self?.fetchGroup() await self?.fetchTransaction() - await self?.getPayerUserDetail() - await self?.getPayableUserDetail() + await self?.getPaymentUsersData() } } @@ -91,8 +91,8 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { // MARK: - Data Loading private func fetchGroup() async { do { - self.group = try await groupRepository.fetchGroupBy(id: groupId) - self.viewState = .initial + group = try await groupRepository.fetchGroupBy(id: groupId) + selectedCurrency = Currency.getCurrencyFromCode(group?.defaultCurrency ?? "INR") LogD("GroupPaymentViewModel: \(#function) Group fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch group \(groupId): \(error).") @@ -104,15 +104,14 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { guard let transactionId else { return } do { - viewState = .loading transaction = try await transactionRepository.fetchTransactionBy(groupId: groupId, - transactionId: transactionId) + transactionId: transactionId) paymentDate = transaction?.date.dateValue() ?? Date.now paymentNote = transaction?.note ?? "" paymentImageUrl = transaction?.imageUrl paymentReason = transaction?.reason ?? "" - - viewState = .initial + let defaultCurrency = group?.defaultCurrency ?? "INR" + selectedCurrency = Currency.getCurrencyFromCode(transaction?.currencyCode ?? defaultCurrency) LogD("GroupPaymentViewModel: \(#function) Payment fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch payment \(transactionId): \(error).") @@ -120,30 +119,19 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { } } - private func getPayerUserDetail() async { + private func getPaymentUsersData() async { do { - viewState = .loading - payer = try await userRepository.fetchUserBy(userID: payerId) + let users = try await userRepository.fetchUsersBy(userIds: [payerId, receiverId]) + payer = users.first(where: { $0.id == payerId }) + receiver = users.first(where: { $0.id == receiverId }) viewState = .initial - LogD("GroupPaymentViewModel: \(#function) Payer fetched successfully.") + LogD("GroupPaymentViewModel: \(#function) users data fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch payer \(payerId): \(error).") handleServiceError() } } - private func getPayableUserDetail() async { - do { - viewState = .loading - receiver = try await userRepository.fetchUserBy(userID: receiverId) - viewState = .initial - LogD("GroupPaymentViewModel: \(#function) Payable fetched successfully.") - } catch { - LogE("GroupPaymentViewModel: \(#function) Failed to fetch payable \(receiverId): \(error).") - handleServiceError() - } - } - // MARK: - User Actions func handleNoteBtnTap() { showAddNoteEditor = true @@ -236,7 +224,6 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { if let transaction { var newTransaction = transaction newTransaction.amount = amount - newTransaction.currencyCode = "INR" newTransaction.date = .init(date: paymentDate) newTransaction.payerId = payerId newTransaction.receiverId = receiverId @@ -244,12 +231,12 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { newTransaction.updatedBy = userId newTransaction.note = paymentNote newTransaction.reason = paymentReason - + newTransaction.currencyCode = selectedCurrency.code return await updateTransaction(transaction: newTransaction, oldTransaction: transaction) } else { let transaction = Transactions(payerId: payerId, receiverId: receiverId, date: .init(date: paymentDate), - addedBy: userId, amount: amount, currencyCode: "INR", note: paymentNote, - reason: paymentReason) + addedBy: userId, amount: amount, currencyCode: selectedCurrency.code, + note: paymentNote, reason: paymentReason) return await addTransaction(transaction: transaction) } } @@ -266,7 +253,6 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { members: (payer, receiver), imageData: getImageData()) NotificationCenter.default.post(name: .addTransaction, object: self.transaction) await updateGroupMemberBalance(updateType: .Add) - showLoader = false LogD("GroupPaymentViewModel: \(#function) Payment added successfully.") return true @@ -287,7 +273,6 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { do { showLoader = true - self.transaction = try await transactionRepository.updateTransactionWithImage(imageData: getImageData(), newImageUrl: paymentImageUrl, group: group, transaction: (transaction, oldTransaction), members: (payer, receiver)) defer { From 2b116f0b4a752b60a939997276db0a10c4eab297 Mon Sep 17 00:00:00 2001 From: Amisha Date: Thu, 16 Jan 2025 19:04:26 +0530 Subject: [PATCH 5/6] Add coderabbit changes --- Data/Data/Model/Currency/Currencies.json | 2 +- Data/Data/Model/Currency/Currency.swift | 8 +- Data/Data/Model/Expense.swift | 11 +- Data/Data/Model/Groups.swift | 4 +- Data/Data/Model/Transaction.swift | 6 +- Data/Data/Repository/CommentRepository.swift | 2 +- Splito.xcodeproj/project.pbxproj | 4 - Splito/Localization/Localizable.xcstrings | 7 +- Splito/Resource/Currencies.json | 934 ------------------ Splito/UI/Home/Expense/AddExpenseView.swift | 3 +- .../UI/Home/Expense/AddExpenseViewModel.swift | 5 +- .../UI/Home/Expense/CurrencyPickerView.swift | 4 +- .../Settle up/Payment/GroupPaymentView.swift | 3 +- .../Payment/GroupPaymentViewModel.swift | 5 +- 14 files changed, 28 insertions(+), 970 deletions(-) delete mode 100644 Splito/Resource/Currencies.json diff --git a/Data/Data/Model/Currency/Currencies.json b/Data/Data/Model/Currency/Currencies.json index 824dc91c..f99e00a9 100644 --- a/Data/Data/Model/Currency/Currencies.json +++ b/Data/Data/Model/Currency/Currencies.json @@ -531,7 +531,7 @@ "code": "MOP", "name": "Macanese Pataca", "symbol": "P", - "region": "M" + "region": "MO" }, { "code": "MRU", diff --git a/Data/Data/Model/Currency/Currency.swift b/Data/Data/Model/Currency/Currency.swift index d4aeb270..98c7a0c1 100644 --- a/Data/Data/Model/Currency/Currency.swift +++ b/Data/Data/Model/Currency/Currency.swift @@ -13,6 +13,8 @@ public struct Currency: Decodable, Hashable { public let symbol: String public let region: String + public static var defaultCurrency = Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN") + public init(code: String, name: String, symbol: String, region: String) { self.code = code self.name = name @@ -25,9 +27,9 @@ public struct Currency: Decodable, Hashable { return allCurrencies } - public static func getCurrencyFromCode(_ code: String) -> Currency { + public static func getCurrencyFromCode(_ code: String?) -> Currency { let allCurrencies = getAllCurrencies() - let currency = allCurrencies.first(where: { $0.code == code }) ?? Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN") + let currency = allCurrencies.first(where: { $0.code == code }) ?? defaultCurrency return currency } @@ -35,7 +37,7 @@ public struct Currency: Decodable, Hashable { let allCurrencies = getAllCurrencies() let currentLocal = Locale.current.region?.identifier let currency = allCurrencies.first(where: { $0.region == currentLocal }) ?? - (allCurrencies.first ?? Currency(code: "INR", name: "Indian Rupee", symbol: "₹", region: "IN")) + (allCurrencies.first ?? defaultCurrency) return currency } } diff --git a/Data/Data/Model/Expense.swift b/Data/Data/Model/Expense.swift index 8793668b..0b4f0547 100644 --- a/Data/Data/Model/Expense.swift +++ b/Data/Data/Model/Expense.swift @@ -15,7 +15,7 @@ public struct Expense: Codable, Hashable, Identifiable { public var name: String public var amount: Double public var category: String? = "General" - public var currencyCode: String? = "INR" + public var currencyCode: String? = Currency.defaultCurrency.code public var date: Timestamp public let addedBy: String public var updatedAt: Timestamp? @@ -29,10 +29,11 @@ public struct Expense: Codable, Hashable, Identifiable { public var participants: [String]? = [] // List of user ids, Used for searching expenses by user public var isActive: Bool - public init(groupId: String, name: String, amount: Double, category: String = "General", currencyCode: String = "INR", - date: Timestamp, addedBy: String, updatedAt: Timestamp? = nil, updatedBy: String? = nil, note: String? = nil, - imageUrl: String? = nil, splitType: SplitType, splitTo: [String], splitData: [String: Double]? = nil, - paidBy: [String: Double], participants: [String], isActive: Bool = true) { + public init(groupId: String, name: String, amount: Double, category: String = "General", + currencyCode: String = Currency.defaultCurrency.code, date: Timestamp, addedBy: String, + updatedAt: Timestamp? = nil, updatedBy: String? = nil, note: String? = nil, imageUrl: String? = nil, + splitType: SplitType, splitTo: [String], splitData: [String: Double]? = nil, paidBy: [String: Double], + participants: [String], isActive: Bool = true) { self.groupId = groupId self.name = name self.amount = amount diff --git a/Data/Data/Model/Groups.swift b/Data/Data/Model/Groups.swift index 6ea8f6d6..901cacbd 100644 --- a/Data/Data/Model/Groups.swift +++ b/Data/Data/Model/Groups.swift @@ -22,13 +22,13 @@ public struct Groups: Codable, Identifiable { public let createdAt: Timestamp public var updatedAt: Timestamp public var hasExpenses: Bool - public var defaultCurrency: String? = "INR" + public var defaultCurrency: String? = Currency.defaultCurrency.code public var isActive: Bool public init(name: String, type: GroupType = .splitExpense, createdBy: String, updatedBy: String? = nil, imageUrl: String? = nil, members: [String], initialBalance: Double = 0.0, balances: [GroupMemberBalance], createdAt: Timestamp = Timestamp(), updatedAt: Timestamp = Timestamp(), hasExpenses: Bool = false, - currencyCode: String = "INR", isActive: Bool = true) { + currencyCode: String = Currency.defaultCurrency.code, isActive: Bool = true) { self.name = name self.type = type self.createdBy = createdBy diff --git a/Data/Data/Model/Transaction.swift b/Data/Data/Model/Transaction.swift index 69f64553..61e73234 100644 --- a/Data/Data/Model/Transaction.swift +++ b/Data/Data/Model/Transaction.swift @@ -16,7 +16,7 @@ public struct Transactions: Codable, Hashable, Identifiable { public var date: Timestamp public let addedBy: String public var amount: Double - public var currencyCode: String? = "INR" + public var currencyCode: String? = Currency.defaultCurrency.code public var updatedBy: String? public var note: String? public var reason: String? @@ -25,8 +25,8 @@ public struct Transactions: Codable, Hashable, Identifiable { public var isActive: Bool public init(payerId: String, receiverId: String, date: Timestamp, addedBy: String, amount: Double, - currencyCode: String? = "INR", updatedBy: String? = nil, note: String? = nil, reason: String? = nil, - imageUrl: String? = nil, updatedAt: Timestamp? = nil, isActive: Bool = true) { + currencyCode: String? = Currency.defaultCurrency.code, updatedBy: String? = nil, note: String? = nil, + reason: String? = nil, imageUrl: String? = nil, updatedAt: Timestamp? = nil, isActive: Bool = true) { self.payerId = payerId self.receiverId = receiverId self.date = date diff --git a/Data/Data/Repository/CommentRepository.swift b/Data/Data/Repository/CommentRepository.swift index c89f206c..18d20476 100644 --- a/Data/Data/Repository/CommentRepository.swift +++ b/Data/Data/Repository/CommentRepository.swift @@ -94,7 +94,7 @@ public class CommentRepository { let payerName = (user.id == transaction.payerId && memberId == transaction.payerId) ? (user.id == transaction.addedBy ? "You" : "you") : (memberId == transaction.payerId) ? "you" : members.payer.nameWithLastInitial - + let receiverName = (memberId == transaction.receiverId) ? "you" : (memberId == transaction.receiverId) ? "you" : members.receiver.nameWithLastInitial context = ActivityLogContext(group: group, transaction: transaction, comment: comment, diff --git a/Splito.xcodeproj/project.pbxproj b/Splito.xcodeproj/project.pbxproj index 8f36cd82..b71249cf 100644 --- a/Splito.xcodeproj/project.pbxproj +++ b/Splito.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ D88721452B9B2C78009DC5BE /* GroupListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88721442B9B2C78009DC5BE /* GroupListView.swift */; }; D88721472B9B2C97009DC5BE /* GroupListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88721462B9B2C97009DC5BE /* GroupListViewModel.swift */; }; D888EA722D2BB41F0003284B /* ExpensesSearchRouteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888EA712D2BB4180003284B /* ExpensesSearchRouteView.swift */; }; - D888EA752D3141460003284B /* Currencies.json in Resources */ = {isa = PBXBuildFile; fileRef = D888EA742D31413C0003284B /* Currencies.json */; }; D889F5B92B7A521F008C6A43 /* SplashView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D889F5B82B7A521F008C6A43 /* SplashView.storyboard */; }; D89684452B722D3400D5F721 /* SplitoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89684442B722D3400D5F721 /* SplitoApp.swift */; }; D89684492B722D3700D5F721 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D89684482B722D3700D5F721 /* Assets.xcassets */; }; @@ -233,7 +232,6 @@ D88721442B9B2C78009DC5BE /* GroupListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListView.swift; sourceTree = ""; }; D88721462B9B2C97009DC5BE /* GroupListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListViewModel.swift; sourceTree = ""; }; D888EA712D2BB4180003284B /* ExpensesSearchRouteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpensesSearchRouteView.swift; sourceTree = ""; }; - D888EA742D31413C0003284B /* Currencies.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Currencies.json; sourceTree = ""; }; D889F5B82B7A521F008C6A43 /* SplashView.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SplashView.storyboard; sourceTree = ""; }; D89684412B722D3400D5F721 /* Splito.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Splito.app; sourceTree = BUILT_PRODUCTS_DIR; }; D89684442B722D3400D5F721 /* SplitoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitoApp.swift; sourceTree = ""; }; @@ -671,7 +669,6 @@ children = ( D89684482B722D3700D5F721 /* Assets.xcassets */, D889F5B82B7A521F008C6A43 /* SplashView.storyboard */, - D888EA742D31413C0003284B /* Currencies.json */, ); path = Resource; sourceTree = ""; @@ -924,7 +921,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D888EA752D3141460003284B /* Currencies.json in Resources */, D896844C2B722D3700D5F721 /* Preview Assets.xcassets in Resources */, D89684492B722D3700D5F721 /* Assets.xcassets in Resources */, D889F5B92B7A521F008C6A43 /* SplashView.storyboard in Resources */, diff --git a/Splito/Localization/Localizable.xcstrings b/Splito/Localization/Localizable.xcstrings index 63875a3a..272988c2 100644 --- a/Splito/Localization/Localizable.xcstrings +++ b/Splito/Localization/Localizable.xcstrings @@ -12,9 +12,6 @@ }, " %@ settled up" : { - }, - " ₹ 0.00" : { - }, " commented on" : { "extractionState" : "manual" @@ -550,7 +547,7 @@ "no balance" : { }, - "No country found for \"%@\"!" : { + "No currency found for \"%@\"!" : { }, "No email address" : { @@ -1074,4 +1071,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Splito/Resource/Currencies.json b/Splito/Resource/Currencies.json deleted file mode 100644 index 717e875d..00000000 --- a/Splito/Resource/Currencies.json +++ /dev/null @@ -1,934 +0,0 @@ -{ - "currencies": [ - { - "code": "AED", - "name": "United Arab Emirates Dirham", - "symbol": "د.إ", - "region": "AE" - }, - { - "code": "AFN", - "name": "Afghan Afghani", - "symbol": "؋", - "region": "AF" - }, - { - "code": "ALL", - "name": "Albanian Lek", - "symbol": "L", - "region": "AL" - }, - { - "code": "AMD", - "name": "Armenian Dram", - "symbol": "֏", - "region": "AM" - }, - { - "code": "ANG", - "name": "Netherlands Antillean Guilder", - "symbol": "ƒ", - "region": "CW" - }, - { - "code": "AOA", - "name": "Angolan Kwanza", - "symbol": "Kz", - "region": "AO" - }, - { - "code": "ARS", - "name": "Argentine Peso", - "symbol": "$", - "region": "AR" - }, - { - "code": "AUD", - "name": "Australian Dollar", - "symbol": "$", - "region": "AU" - }, - { - "code": "AWG", - "name": "Aruban Florin", - "symbol": "ƒ", - "region": "AW" - }, - { - "code": "AZN", - "name": "Azerbaijani Manat", - "symbol": "₼", - "region": "AZ" - }, - { - "code": "BAM", - "name": "Bosnia and Herzegovina Convertible Mark", - "symbol": "KM", - "region": "BA" - }, - { - "code": "BBD", - "name": "Barbadian Dollar", - "symbol": "$", - "region": "BB" - }, - { - "code": "BDT", - "name": "Bangladeshi Taka", - "symbol": "৳", - "region": "BD" - }, - { - "code": "BGN", - "name": "Bulgarian Lev", - "symbol": "лв", - "region": "BG" - }, - { - "code": "BHD", - "name": "Bahraini Dinar", - "symbol": ".د.ب", - "region": "BH" - }, - { - "code": "BIF", - "name": "Burundian Franc", - "symbol": "FBu", - "region": "BI" - }, - { - "code": "BMD", - "name": "Bermudian Dollar", - "symbol": "$", - "region": "BM" - }, - { - "code": "BND", - "name": "Brunei Dollar", - "symbol": "$", - "region": "BN" - }, - { - "code": "BOB", - "name": "Bolivian Boliviano", - "symbol": "Bs.", - "region": "BO" - }, - { - "code": "BRL", - "name": "Brazilian Real", - "symbol": "R$", - "region": "BR" - }, - { - "code": "BSD", - "name": "Bahamian Dollar", - "symbol": "$", - "region": "BS" - }, - { - "code": "BTN", - "name": "Bhutanese Ngultrum", - "symbol": "Nu.", - "region": "BT" - }, - { - "code": "BWP", - "name": "Botswana Pula", - "symbol": "P", - "region": "BW" - }, - { - "code": "BYN", - "name": "Belarusian Ruble", - "symbol": "Br", - "region": "BY" - }, - { - "code": "BZD", - "name": "Belize Dollar", - "symbol": "BZ$", - "region": "BZ" - }, - { - "code": "CAD", - "name": "Canadian Dollar", - "symbol": "$", - "region": "CA" - }, - { - "code": "CDF", - "name": "Congolese Franc", - "symbol": "FC", - "region": "CD" - }, - { - "code": "CHF", - "name": "Swiss Franc", - "symbol": "CHF", - "region": "CH" - }, - { - "code": "CLP", - "name": "Chilean Peso", - "symbol": "$", - "region": "CL" - }, - { - "code": "CNY", - "name": "Chinese Yuan", - "symbol": "¥", - "region": "CN" - }, - { - "code": "COP", - "name": "Colombian Peso", - "symbol": "$", - "region": "CO" - }, - { - "code": "CRC", - "name": "Costa Rican Colón", - "symbol": "₡", - "region": "CR" - }, - { - "code": "CUP", - "name": "Cuban Peso", - "symbol": "₱", - "region": "CU" - }, - { - "code": "CVE", - "name": "Cape Verdean Escudo", - "symbol": "$", - "region": "CV" - }, - { - "code": "CZK", - "name": "Czech Koruna", - "symbol": "Kč", - "region": "CZ" - }, - { - "code": "DJF", - "name": "Djiboutian Franc", - "symbol": "Fdj", - "region": "DJ" - }, - { - "code": "DKK", - "name": "Danish Krone", - "symbol": "kr", - "region": "DK" - }, - { - "code": "DOP", - "name": "Dominican Peso", - "symbol": "RD$", - "region": "DO" - }, - { - "code": "DZD", - "name": "Algerian Dinar", - "symbol": "د.ج", - "region": "DZ" - }, - { - "code": "EGP", - "name": "Egyptian Pound", - "symbol": "£", - "region": "EG" - }, - { - "code": "ERN", - "name": "Eritrean Nakfa", - "symbol": "Nfk", - "region": "ER" - }, - { - "code": "ETB", - "name": "Ethiopian Birr", - "symbol": "Br", - "region": "ET" - }, - { - "code": "EUR", - "name": "Euro", - "symbol": "€", - "region": "EU" - }, - { - "code": "FJD", - "name": "Fijian Dollar", - "symbol": "$", - "region": "FJ" - }, - { - "code": "FKP", - "name": "Falkland Islands Pound", - "symbol": "£", - "region": "FK" - }, - { - "code": "GBP", - "name": "British Pound", - "symbol": "£", - "region": "GB" - }, - { - "code": "GEL", - "name": "Georgian Lari", - "symbol": "₾", - "region": "GE" - }, - { - "code": "GHS", - "name": "Ghanaian Cedi", - "symbol": "₵", - "region": "GH" - }, - { - "code": "GIP", - "name": "Gibraltar Pound", - "symbol": "£", - "region": "GI" - }, - { - "code": "GMD", - "name": "Gambian Dalasi", - "symbol": "D", - "region": "GM" - }, - { - "code": "GNF", - "name": "Guinean Franc", - "symbol": "FG", - "region": "GN" - }, - { - "code": "GTQ", - "name": "Guatemalan Quetzal", - "symbol": "Q", - "region": "GT" - }, - { - "code": "GYD", - "name": "Guyanese Dollar", - "symbol": "$", - "region": "GY" - }, - { - "code": "HKD", - "name": "Hong Kong Dollar", - "symbol": "$", - "region": "HK" - }, - { - "code": "HNL", - "name": "Honduran Lempira", - "symbol": "L", - "region": "HN" - }, - { - "code": "HRK", - "name": "Croatian Kuna", - "symbol": "kn", - "region": "HR" - }, - { - "code": "HTG", - "name": "Haitian Gourde", - "symbol": "G", - "region": "HT" - }, - { - "code": "HUF", - "name": "Hungarian Forint", - "symbol": "Ft", - "region": "HU" - }, - { - "code": "IDR", - "name": "Indonesian Rupiah", - "symbol": "Rp", - "region": "ID" - }, - { - "code": "ILS", - "name": "Israeli New Shekel", - "symbol": "₪", - "region": "IL" - }, - { - "code": "INR", - "name": "Indian Rupee", - "symbol": "₹", - "region": "IN" - }, - { - "code": "IQD", - "name": "Iraqi Dinar", - "symbol": "ع.د", - "region": "IQ" - }, - { - "code": "IRR", - "name": "Iranian Rial", - "symbol": "﷼", - "region": "IR" - }, - { - "code": "ISK", - "name": "Icelandic Króna", - "symbol": "kr", - "region": "IS" - }, - { - "code": "JMD", - "name": "Jamaican Dollar", - "symbol": "J$", - "region": "JM" - }, - { - "code": "JOD", - "name": "Jordanian Dinar", - "symbol": "د.ا", - "region": "JO" - }, - { - "code": "JPY", - "name": "Japanese Yen", - "symbol": "¥", - "region": "JP" - }, - { - "code": "KES", - "name": "Kenyan Shilling", - "symbol": "KSh", - "region": "KE" - }, - { - "code": "KGS", - "name": "Kyrgyzstani Som", - "symbol": "с", - "region": "KG" - }, - { - "code": "KHR", - "name": "Cambodian Riel", - "symbol": "៛", - "region": "KH" - }, - { - "code": "KMF", - "name": "Comorian Franc", - "symbol": "CF", - "region": "KM" - }, - { - "code": "KPW", - "name": "North Korean Won", - "symbol": "₩", - "region": "KP" - }, - { - "code": "KRW", - "name": "South Korean Won", - "symbol": "₩", - "region": "KR" - }, - { - "code": "KWD", - "name": "Kuwaiti Dinar", - "symbol": "د.ك", - "region": "KW" - }, - { - "code": "KYD", - "name": "Cayman Islands Dollar", - "symbol": "$", - "region": "KY" - }, - { - "code": "KZT", - "name": "Kazakhstani Tenge", - "symbol": "₸", - "region": "KZ" - }, - { - "code": "LAK", - "name": "Lao Kip", - "symbol": "₭", - "region": "LA" - }, - { - "code": "LBP", - "name": "Lebanese Pound", - "symbol": "ل.ل", - "region": "LB" - }, - { - "code": "LKR", - "name": "Sri Lankan Rupee", - "symbol": "₨", - "region": "LK" - }, - { - "code": "LRD", - "name": "Liberian Dollar", - "symbol": "$", - "region": "LR" - }, - { - "code": "LSL", - "name": "Lesotho Loti", - "symbol": "L", - "region": "LS" - }, - { - "code": "LYD", - "name": "Libyan Dinar", - "symbol": "ل.د", - "region": "LY" - }, - { - "code": "MAD", - "name": "Moroccan Dirham", - "symbol": "د.م.", - "region": "MA" - }, - { - "code": "MDL", - "name": "Moldovan Leu", - "symbol": "L", - "region": "MD" - }, - { - "code": "MGA", - "name": "Malagasy Ariary", - "symbol": "Ar", - "region": "MG" - }, - { - "code": "MKD", - "name": "Macedonian Denar", - "symbol": "ден", - "region": "MK" - }, - { - "code": "MMK", - "name": "Myanmar Kyat", - "symbol": "Ks", - "region": "MM" - }, - { - "code": "MNT", - "name": "Mongolian Tögrög", - "symbol": "₮", - "region": "MN" - }, - { - "code": "MOP", - "name": "Macanese Pataca", - "symbol": "P", - "region": "M" - }, - { - "code": "MRU", - "name": "Mauritanian Ouguiya", - "symbol": "UM", - "region": "MR" - }, - { - "code": "MUR", - "name": "Mauritian Rupee", - "symbol": "₨", - "region": "MU" - }, - { - "code": "MVR", - "name": "Maldivian Rufiyaa", - "symbol": ".ރ", - "region": "MV" - }, - { - "code": "MWK", - "name": "Malawian Kwacha", - "symbol": "MK", - "region": "MW" - }, - { - "code": "MXN", - "name": "Mexican Peso", - "symbol": "$", - "region": "MX" - }, - { - "code": "MYR", - "name": "Malaysian Ringgit", - "symbol": "RM", - "region": "MY" - }, - { - "code": "MZN", - "name": "Mozambican Metical", - "symbol": "MT", - "region": "MZ" - }, - { - "code": "NAD", - "name": "Namibian Dollar", - "symbol": "$", - "region": "NA" - }, - { - "code": "NGN", - "name": "Nigerian Naira", - "symbol": "₦", - "region": "NG" - }, - { - "code": "NIO", - "name": "Nicaraguan Córdoba", - "symbol": "C$", - "region": "NI" - }, - { - "code": "NOK", - "name": "Norwegian Krone", - "symbol": "kr", - "region": "NO" - }, - { - "code": "NPR", - "name": "Nepalese Rupee", - "symbol": "₨", - "region": "NP" - }, - { - "code": "NZD", - "name": "New Zealand Dollar", - "symbol": "$", - "region": "NZ" - }, - { - "code": "OMR", - "name": "Omani Rial", - "symbol": "ر.ع.", - "region": "OM" - }, - { - "code": "PAB", - "name": "Panamanian Balboa", - "symbol": "B/.", - "region": "PA" - }, - { - "code": "PEN", - "name": "Peruvian Sol", - "symbol": "S/", - "region": "PE" - }, - { - "code": "PGK", - "name": "Papua New Guinean Kina", - "symbol": "K", - "region": "PG" - }, - { - "code": "PHP", - "name": "Philippine Peso", - "symbol": "₱", - "region": "PH" - }, - { - "code": "PKR", - "name": "Pakistani Rupee", - "symbol": "₨", - "region": "PK" - }, - { - "code": "PLN", - "name": "Polish Złoty", - "symbol": "zł", - "region": "PL" - }, - { - "code": "PYG", - "name": "Paraguayan Guaraní", - "symbol": "₲", - "region": "PY" - }, - { - "code": "QAR", - "name": "Qatari Riyal", - "symbol": "ر.ق", - "region": "QA" - }, - { - "code": "RON", - "name": "Romanian Leu", - "symbol": "lei", - "region": "RO" - }, - { - "code": "RSD", - "name": "Serbian Dinar", - "symbol": "дин.", - "region": "RS" - }, - { - "code": "RUB", - "name": "Russian Ruble", - "symbol": "₽", - "region": "RU" - }, - { - "code": "RWF", - "name": "Rwandan Franc", - "symbol": "FRw", - "region": "RW" - }, - { - "code": "SAR", - "name": "Saudi Riyal", - "symbol": "ر.س", - "region": "SA" - }, - { - "code": "SBD", - "name": "Solomon Islands Dollar", - "symbol": "$", - "region": "SB" - }, - { - "code": "SCR", - "name": "Seychellois Rupee", - "symbol": "₨", - "region": "SC" - }, - { - "code": "SDG", - "name": "Sudanese Pound", - "symbol": "ج.س.", - "region": "SD" - }, - { - "code": "SEK", - "name": "Swedish Krona", - "symbol": "kr", - "region": "SE" - }, - { - "code": "SGD", - "name": "Singapore Dollar", - "symbol": "$", - "region": "SG" - }, - { - "code": "SHP", - "name": "Saint Helena Pound", - "symbol": "£", - "region": "SH" - }, - { - "code": "SLL", - "name": "Sierra Leonean Leone", - "symbol": "Le", - "region": "SL" - }, - { - "code": "SOS", - "name": "Somali Shilling", - "symbol": "Sh", - "region": "SO" - }, - { - "code": "SRD", - "name": "Surinamese Dollar", - "symbol": "$", - "region": "SR" - }, - { - "code": "SSP", - "name": "South Sudanese Pound", - "symbol": "£", - "region": "SS" - }, - { - "code": "STN", - "name": "São Tomé and Príncipe Dobra", - "symbol": "Db", - "region": "ST" - }, - { - "code": "SYP", - "name": "Syrian Pound", - "symbol": "£", - "region": "SY" - }, - { - "code": "SZL", - "name": "Swazi Lilangeni", - "symbol": "L", - "region": "SZ" - }, - { - "code": "THB", - "name": "Thai Baht", - "symbol": "฿", - "region": "TH" - }, - { - "code": "TJS", - "name": "Tajikistani Somoni", - "symbol": "ЅМ", - "region": "TJ" - }, - { - "code": "TMT", - "name": "Turkmenistan Manat", - "symbol": "m", - "region": "TM" - }, - { - "code": "TND", - "name": "Tunisian Dinar", - "symbol": "د.ت", - "region": "TN" - }, - { - "code": "TOP", - "name": "Tongan Paʻanga", - "symbol": "T$", - "region": "TO" - }, - { - "code": "TRY", - "name": "Turkish Lira", - "symbol": "₺", - "region": "TR" - }, - { - "code": "TTD", - "name": "Trinidad and Tobago Dollar", - "symbol": "$", - "region": "TT" - }, - { - "code": "TWD", - "name": "New Taiwan Dollar", - "symbol": "NT$", - "region": "TW" - }, - { - "code": "TZS", - "name": "Tanzanian Shilling", - "symbol": "TSh", - "region": "TZ" - }, - { - "code": "UAH", - "name": "Ukrainian Hryvnia", - "symbol": "₴", - "region": "UA" - }, - { - "code": "UGX", - "name": "Ugandan Shilling", - "symbol": "USh", - "region": "UG" - }, - { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "region": "US" - }, - { - "code": "UYU", - "name": "Uruguayan Peso", - "symbol": "$", - "region": "UY" - }, - { - "code": "UZS", - "name": "Uzbekistani Som", - "symbol": "so'm", - "region": "UZ" - }, - { - "code": "VES", - "name": "Venezuelan Bolívar Soberano", - "symbol": "Bs.", - "region": "VE" - }, - { - "code": "VND", - "name": "Vietnamese Đồng", - "symbol": "₫", - "region": "VN" - }, - { - "code": "VUV", - "name": "Vanuatu Vatu", - "symbol": "VT", - "region": "VU" - }, - { - "code": "WST", - "name": "Samoan Tālā", - "symbol": "T", - "region": "WS" - }, - { - "code": "XAF", - "name": "Central African CFA Franc", - "symbol": "FCFA", - "region": "CM" - }, - { - "code": "XCD", - "name": "East Caribbean Dollar", - "symbol": "$", - "region": "AG" - }, - { - "code": "XOF", - "name": "West African CFA Franc", - "symbol": "CFA", - "region": "BJ" - }, - { - "code": "XPF", - "name": "CFP Franc", - "symbol": "₣", - "region": "PF" - }, - { - "code": "YER", - "name": "Yemeni Rial", - "symbol": "﷼", - "region": "YE" - }, - { - "code": "ZAR", - "name": "South African Rand", - "symbol": "R", - "region": "ZA" - }, - { - "code": "ZMW", - "name": "Zambian Kwacha", - "symbol": "ZK", - "region": "ZM" - }, - { - "code": "ZWL", - "name": "Zimbabwean Dollar", - "symbol": "$", - "region": "ZW" - } - ] -} diff --git a/Splito/UI/Home/Expense/AddExpenseView.swift b/Splito/UI/Home/Expense/AddExpenseView.swift index 1f819faa..288770dc 100644 --- a/Splito/UI/Home/Expense/AddExpenseView.swift +++ b/Splito/UI/Home/Expense/AddExpenseView.swift @@ -99,8 +99,7 @@ struct AddExpenseView: View { image: $viewModel.expenseImage, isPresented: $viewModel.showImagePicker) } .sheet(isPresented: $viewModel.showCurrencyPicker) { - let currencies = Currency.getAllCurrencies() - CurrencyPickerView(currencies: currencies, selectedCurrency: $viewModel.selectedCurrency, + CurrencyPickerView(selectedCurrency: $viewModel.selectedCurrency, isPresented: $viewModel.showCurrencyPicker) } .toolbar { diff --git a/Splito/UI/Home/Expense/AddExpenseViewModel.swift b/Splito/UI/Home/Expense/AddExpenseViewModel.swift index e8977759..ebee5726 100644 --- a/Splito/UI/Home/Expense/AddExpenseViewModel.swift +++ b/Splito/UI/Home/Expense/AddExpenseViewModel.swift @@ -78,7 +78,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { viewState = .loading await fetchAndUpdateGroupData(groupId: groupId) selectedPayers = [userId: expenseAmount] - selectedCurrency = Currency.getCurrencyFromCode(selectedGroup?.defaultCurrency ?? "INR") + selectedCurrency = Currency.getCurrencyFromCode(selectedGroup?.defaultCurrency) viewState = .initial LogD("AddExpenseViewModel: \(#function) Group fetched successfully.") } @@ -119,8 +119,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { selectedPayers = expense.paidBy expenseImageUrl = expense.imageUrl expenseNote = expense.note ?? "" - let defaultCurrency = selectedGroup?.defaultCurrency ?? "INR" - selectedCurrency = Currency.getCurrencyFromCode(expense.currencyCode ?? defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(expense.currencyCode ?? selectedGroup?.defaultCurrency) if let splitData = expense.splitData { self.splitData = splitData } diff --git a/Splito/UI/Home/Expense/CurrencyPickerView.swift b/Splito/UI/Home/Expense/CurrencyPickerView.swift index 4c39c01a..86332dc0 100644 --- a/Splito/UI/Home/Expense/CurrencyPickerView.swift +++ b/Splito/UI/Home/Expense/CurrencyPickerView.swift @@ -13,7 +13,6 @@ struct CurrencyPickerView: View { @Environment(\.dismiss) var dismiss - var currencies: [Currency] @Binding var selectedCurrency: Currency @Binding var isPresented: Bool @@ -21,6 +20,7 @@ struct CurrencyPickerView: View { @FocusState private var isFocused: Bool private var filteredCurrencies: [Currency] { + let currencies = Currency.getAllCurrencies() guard !searchedCurrency.isEmpty else { return currencies } return currencies.filter { currency in currency.name.lowercased().contains(searchedCurrency.lowercased()) || @@ -74,7 +74,7 @@ private struct CurrencyNotFoundView: View { var body: some View { VStack(spacing: 0) { Spacer() - Text("No country found for \"\(searchedCurrency)\"!") + Text("No currency found for \"\(searchedCurrency)\"!") .font(.subTitle1()) .foregroundStyle(disableText) .padding(.bottom, 60) diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift index 78a42b1f..7f6823c7 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift @@ -112,8 +112,7 @@ struct GroupPaymentView: View { image: $viewModel.paymentImage, isPresented: $viewModel.showImagePicker) } .sheet(isPresented: $viewModel.showCurrencyPicker) { - let currencies = Currency.getAllCurrencies() - CurrencyPickerView(currencies: currencies, selectedCurrency: $viewModel.selectedCurrency, + CurrencyPickerView(selectedCurrency: $viewModel.selectedCurrency, isPresented: $viewModel.showCurrencyPicker) } .sheet(isPresented: $viewModel.showAddNoteEditor) { diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift index 8bfdd5dc..a484e91b 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift @@ -93,7 +93,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { private func fetchGroup() async { do { group = try await groupRepository.fetchGroupBy(id: groupId) - selectedCurrency = Currency.getCurrencyFromCode(group?.defaultCurrency ?? "INR") + selectedCurrency = Currency.getCurrencyFromCode(group?.defaultCurrency) LogD("GroupPaymentViewModel: \(#function) Group fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch group \(groupId): \(error).") @@ -111,8 +111,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { paymentNote = transaction?.note ?? "" paymentImageUrl = transaction?.imageUrl paymentReason = transaction?.reason ?? "" - let defaultCurrency = group?.defaultCurrency ?? "INR" - selectedCurrency = Currency.getCurrencyFromCode(transaction?.currencyCode ?? defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(transaction?.currencyCode ?? group?.defaultCurrency) LogD("GroupPaymentViewModel: \(#function) Payment fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch payment \(transactionId): \(error).") From 3ad383f278365a1dc69d6bca1b7a454037488e11 Mon Sep 17 00:00:00 2001 From: Amisha Date: Fri, 17 Jan 2025 16:58:05 +0530 Subject: [PATCH 6/6] Add currency in the group balance --- Data/Data/Extension/Double+Extension.swift | 2 - Data/Data/Model/Groups.swift | 39 +-- Splito/Localization/Localizable.xcstrings | 26 +- .../UI/Home/Expense/AddExpenseViewModel.swift | 4 +- .../Groups/CalculateExpensesFunctions.swift | 244 +++++++++++------- .../Create Group/CreateGroupViewModel.swift | 2 +- .../Balances/GroupBalancesView.swift | 78 +++--- .../Balances/GroupBalancesViewModel.swift | 49 ++-- .../Settle up/GroupSettleUpView.swift | 24 +- .../Settle up/GroupSettleUpViewModel.swift | 23 +- .../Payment/GroupPaymentViewModel.swift | 4 +- .../Totals/GroupTotalsViewModel.swift | 6 +- .../Group Setting/GroupSettingView.swift | 37 +-- .../Group Setting/GroupSettingViewModel.swift | 25 +- .../Groups/Group/GroupExpenseListView.swift | 35 ++- .../Groups/Group/GroupHomeViewModel.swift | 9 +- .../UI/Home/Groups/GroupListViewModel.swift | 31 ++- .../Home/Groups/GroupListWithDetailView.swift | 25 +- 18 files changed, 407 insertions(+), 256 deletions(-) diff --git a/Data/Data/Extension/Double+Extension.swift b/Data/Data/Extension/Double+Extension.swift index 938bec65..3dfee58e 100644 --- a/Data/Data/Extension/Double+Extension.swift +++ b/Data/Data/Extension/Double+Extension.swift @@ -10,7 +10,6 @@ import Foundation public extension Double { var formattedCurrency: String { let formatter = NumberFormatter() - formatter.numberStyle = .currency formatter.locale = Locale.current if let formattedAmount = formatter.string(from: NSNumber(value: self)) { @@ -35,7 +34,6 @@ public extension Double { var formattedCurrencyWithSign: String { let formatter = NumberFormatter() - formatter.numberStyle = .currency formatter.locale = Locale.current if let formattedAmount = formatter.string(from: NSNumber(value: self)) { diff --git a/Data/Data/Model/Groups.swift b/Data/Data/Model/Groups.swift index 901cacbd..2acd30a4 100644 --- a/Data/Data/Model/Groups.swift +++ b/Data/Data/Model/Groups.swift @@ -22,9 +22,13 @@ public struct Groups: Codable, Identifiable { public let createdAt: Timestamp public var updatedAt: Timestamp public var hasExpenses: Bool - public var defaultCurrency: String? = Currency.defaultCurrency.code + private var defaultCurrency: String? = Currency.defaultCurrency.code public var isActive: Bool + public var defaultCurrencyCode: String { + defaultCurrency ?? Currency.defaultCurrency.code + } + public init(name: String, type: GroupType = .splitExpense, createdBy: String, updatedBy: String? = nil, imageUrl: String? = nil, members: [String], initialBalance: Double = 0.0, balances: [GroupMemberBalance], createdAt: Timestamp = Timestamp(), updatedAt: Timestamp = Timestamp(), hasExpenses: Bool = false, @@ -69,31 +73,34 @@ public enum GroupType: String, Codable { public struct GroupMemberBalance: Codable { public let id: String /// Member Id + public var balanceByCurrency: [String: GroupCurrencyBalance] /// Currency wise member balance + + public init(id: String, balanceByCurrency: [String: GroupCurrencyBalance]) { + self.id = id + self.balanceByCurrency = balanceByCurrency + } + + enum CodingKeys: String, CodingKey { + case id + case balanceByCurrency = "balance_by_currency" + } +} + +public struct GroupCurrencyBalance: Codable { public var balance: Double public var totalSummary: [GroupTotalSummary] - public init(id: String, balance: Double, totalSummary: [GroupTotalSummary]) { - self.id = id + public init(balance: Double, totalSummary: [GroupTotalSummary]) { self.balance = balance self.totalSummary = totalSummary } enum CodingKeys: String, CodingKey { - case id case balance case totalSummary = "total_summary" } } -// public struct GroupMemberBalance: Codable { -// public let id: String /// Member Id -// public var balanceByCurrency: [String: GroupCurrencyBalance] /// Currency wise member balance -// } -// public struct GroupCurrencyBalance: Codable { -// public var balance: Double -// public var totalSummary: [GroupTotalSummary] -// } - public struct GroupTotalSummary: Codable { public var year: Int public var month: Int @@ -149,12 +156,12 @@ public struct GroupMemberSummary: Codable { // MARK: - To show group and expense together public struct GroupInformation { public let group: Groups - public let userBalance: Double - public let memberOweAmount: [String: Double] + public let userBalance: [String: Double] + public let memberOweAmount: [String: [String: Double]] public let members: [AppUser] public let hasExpenses: Bool - public init(group: Groups, userBalance: Double, memberOweAmount: [String: Double], + public init(group: Groups, userBalance: [String: Double], memberOweAmount: [String: [String: Double]], members: [AppUser], hasExpenses: Bool) { self.group = group self.userBalance = userBalance diff --git a/Splito/Localization/Localizable.xcstrings b/Splito/Localization/Localizable.xcstrings index 272988c2..3ade5fbe 100644 --- a/Splito/Localization/Localizable.xcstrings +++ b/Splito/Localization/Localizable.xcstrings @@ -22,6 +22,16 @@ " in" : { "extractionState" : "manual" }, + " in %@ to %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : " in %1$@ to %2$@" + } + } + } + }, " in total" : { }, @@ -30,9 +40,6 @@ }, " to" : { "extractionState" : "manual" - }, - " to %@" : { - }, " updated the group name from" : { "extractionState" : "manual" @@ -42,6 +49,16 @@ }, "%@" : { + }, + "%@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@" + } + } + } }, "%@ %@ " : { "localizations" : { @@ -975,9 +992,6 @@ }, "You" : { "extractionState" : "manual" - }, - "You %@" : { - }, "You are all settle up!" : { diff --git a/Splito/UI/Home/Expense/AddExpenseViewModel.swift b/Splito/UI/Home/Expense/AddExpenseViewModel.swift index ebee5726..2d147004 100644 --- a/Splito/UI/Home/Expense/AddExpenseViewModel.swift +++ b/Splito/UI/Home/Expense/AddExpenseViewModel.swift @@ -78,7 +78,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { viewState = .loading await fetchAndUpdateGroupData(groupId: groupId) selectedPayers = [userId: expenseAmount] - selectedCurrency = Currency.getCurrencyFromCode(selectedGroup?.defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(selectedGroup?.defaultCurrencyCode) viewState = .initial LogD("AddExpenseViewModel: \(#function) Group fetched successfully.") } @@ -119,7 +119,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { selectedPayers = expense.paidBy expenseImageUrl = expense.imageUrl expenseNote = expense.note ?? "" - selectedCurrency = Currency.getCurrencyFromCode(expense.currencyCode ?? selectedGroup?.defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(expense.currencyCode ?? selectedGroup?.defaultCurrencyCode) if let splitData = expense.splitData { self.splitData = splitData } diff --git a/Splito/UI/Home/Groups/CalculateExpensesFunctions.swift b/Splito/UI/Home/Groups/CalculateExpensesFunctions.swift index 45503089..f3549a98 100644 --- a/Splito/UI/Home/Groups/CalculateExpensesFunctions.swift +++ b/Splito/UI/Home/Groups/CalculateExpensesFunctions.swift @@ -15,63 +15,88 @@ public struct Settlement { let sender: String let receiver: String let amount: Double + let currency: String } -public func calculateExpensesSimplified(userId: String, memberBalances: [GroupMemberBalance]) -> ([String: Double]) { - - var memberOwingAmount: [String: Double] = [:] +public func calculateExpensesSimplified(userId: String, + memberBalances: [GroupMemberBalance]) -> [String: [String: Double]] { + var memberOwingAmount: [String: [String: Double]] = [:] + // Calculate settlements based on balances let settlements = calculateSettlements(balances: memberBalances) + + // Loop over each settlement to calculate owed amounts for settlement in settlements where settlement.sender == userId || settlement.receiver == userId { let memberId = settlement.receiver == userId ? settlement.sender : settlement.receiver let amount = settlement.sender == userId ? -settlement.amount : settlement.amount - memberOwingAmount[memberId, default: 0] = amount + + // Add the calculated amount to the corresponding currency and member + memberOwingAmount[settlement.currency, default: [:]][memberId, default: 0] += amount + } + + // Remove entries where the amount is zero for all currencies + memberOwingAmount = memberOwingAmount.mapValues { currencyAmounts in + currencyAmounts.filter { $0.value != 0 } } - return memberOwingAmount.filter { $0.value != 0 } + return memberOwingAmount +} + +/// Helper function to extract all currencies from the balances +private func extractAllCurrencies(from balances: [GroupMemberBalance]) -> [String] { + var currencies: Set = [] + for balance in balances { + currencies.formUnion(balance.balanceByCurrency.keys) + } + return Array(currencies) } /// To decide who owes -> how much amount -> to whom func calculateSettlements(balances: [GroupMemberBalance]) -> [Settlement] { - var creditors: [(String, Double)] = [] - var debtors: [(String, Double)] = [] + var settlements: [Settlement] = [] - // Separate creditors and debtors - for balance in balances { - if balance.balance > 0 { - creditors.append((balance.id, balance.balance)) - } else if balance.balance < 0 { - debtors.append((balance.id, -balance.balance)) // Store positive value for easier calculation + for currency in extractAllCurrencies(from: balances) { + var creditors: [(String, Double)] = [] + var debtors: [(String, Double)] = [] + + // Separate creditors and debtors for the current currency + for balance in balances { + if let currencyBalance = balance.balanceByCurrency[currency]?.balance { + if currencyBalance > 0 { + creditors.append((balance.id, currencyBalance)) + } else if currencyBalance < 0 { + debtors.append((balance.id, -currencyBalance)) // Store positive value for easier calculation + } + } } - } - // Sort creditors and debtors by the amount they owe or are owed - creditors.sort { $0.1 < $1.1 } - debtors.sort { $0.1 < $1.1 } + // Sort creditors and debtors by the amount they owe or are owed + creditors.sort { $0.1 < $1.1 } + debtors.sort { $0.1 < $1.1 } - var i = 0 // creditors index - var j = 0 // debtors index - var settlements: [Settlement] = [] + var i = 0 // creditors index + var j = 0 // debtors index - // Calculate settlements - while i < creditors.count && j < debtors.count { // Process all debts - var (creditor, credAmt) = creditors[i] - var (debtor, debtAmt) = debtors[j] - let minAmount = min(credAmt, debtAmt) + // Calculate settlements for the current currency + while i < creditors.count && j < debtors.count { + var (creditor, credAmt) = creditors[i] + var (debtor, debtAmt) = debtors[j] + let minAmount = min(credAmt, debtAmt) - settlements.append(Settlement(sender: debtor, receiver: creditor, amount: minAmount)) + settlements.append(Settlement(sender: debtor, receiver: creditor, amount: minAmount, currency: currency)) - // Update the amounts - credAmt -= minAmount - debtAmt -= minAmount + // Update the amounts + credAmt -= minAmount + debtAmt -= minAmount - // If the remaining amount is close to zero, treat it as zero - creditors[i].1 = round(credAmt * 100) / 100 - debtors[j].1 = round(debtAmt * 100) / 100 + // If the remaining amount is close to zero, treat it as zero + creditors[i].1 = round(credAmt * 100) / 100 + debtors[j].1 = round(debtAmt * 100) / 100 - // Move the index forward if someone's balance is settled - if creditors[i].1 == 0 { i += 1 } - if debtors[j].1 == 0 { j += 1 } + // Move the index forward if someone's balance is settled + if creditors[i].1 == 0 { i += 1 } + if debtors[j].1 == 0 { j += 1 } + } } return settlements @@ -107,6 +132,7 @@ func getLatestSummaryFrom(totalSummary: [GroupTotalSummary], date: Date) -> Grou public func getUpdatedMemberBalanceFor(expense: Expense, group: Groups, updateType: ExpenseUpdateType) -> [GroupMemberBalance] { var memberBalance = group.balances let expenseDate = expense.date.dateValue() + let currency = expense.currencyCode ?? Currency.defaultCurrency.code for member in group.members { let newSplitAmount = expense.getCalculatedSplitAmountOf(member: member) @@ -116,72 +142,81 @@ public func getUpdatedMemberBalanceFor(expense: Expense, group: Groups, updateTy if let index = memberBalance.firstIndex(where: { $0.id == member }) { switch updateType { case .Add: - memberBalance[index].balance += newSplitAmount + memberBalance[index].balanceByCurrency[currency]?.balance += newSplitAmount // Update the corresponding total summary if it exists for the expense date - if var totalSummary = getLatestSummaryFrom(totalSummary: memberBalance[index].totalSummary, date: expenseDate)?.summary, - let summaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[index].totalSummary, date: expenseDate) { + let groupTotalSummary = memberBalance[index].balanceByCurrency[currency]?.totalSummary ?? [] + if var totalSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: expenseDate)?.summary, + let summaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: expenseDate) { totalSummary.groupTotalSpending += expense.amount totalSummary.totalPaidAmount += (expense.paidBy[member] ?? 0) totalSummary.totalShare += totalSplitAmount totalSummary.changeInBalance = (totalSummary.totalPaidAmount - totalSummary.totalShare) - totalSummary.receivedAmount + totalSummary.paidAmount - memberBalance[index].totalSummary[summaryIndex].summary = totalSummary + memberBalance[index].balanceByCurrency[currency]?.totalSummary[summaryIndex].summary = totalSummary } else { // If no summary exists for the date, create a new summary let summary = getInitialGroupSummaryFor(member: member, expense: expense) - memberBalance[index].totalSummary.append(summary) + memberBalance[index].balanceByCurrency[currency]?.totalSummary.append(summary) } case .Update(let oldExpense): let oldSplitAmount = oldExpense.getTotalSplitAmountOf(member: member) // Update the old date's summary by reversing the old expense values - if let oldSummaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[index].totalSummary, + let groupTotalSummary = memberBalance[index].balanceByCurrency[currency]?.totalSummary ?? [] + if let oldSummaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: oldExpense.date.dateValue()) { - var oldSummary = memberBalance[index].totalSummary[oldSummaryIndex].summary + var oldSummary = groupTotalSummary[oldSummaryIndex].summary oldSummary.groupTotalSpending -= oldExpense.amount oldSummary.totalPaidAmount -= (oldExpense.paidBy[member] ?? 0) oldSummary.totalShare -= abs(oldSplitAmount) oldSummary.changeInBalance = (oldSummary.totalPaidAmount - oldSummary.totalShare) - oldSummary.receivedAmount + oldSummary.paidAmount - memberBalance[index].totalSummary[oldSummaryIndex].summary = oldSummary + memberBalance[index].balanceByCurrency[currency]?.totalSummary[oldSummaryIndex].summary = oldSummary } let oldCalculatedSplitAmount = oldExpense.getCalculatedSplitAmountOf(member: member) - memberBalance[index].balance += (newSplitAmount - oldCalculatedSplitAmount) + memberBalance[index].balanceByCurrency[currency]?.balance += (newSplitAmount - oldCalculatedSplitAmount) // Update the new date's summary - if var newSummary = getLatestSummaryFrom(totalSummary: memberBalance[index].totalSummary, date: expenseDate)?.summary, - let newSummaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[index].totalSummary, date: expenseDate) { + if var newSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: expenseDate)?.summary, + let newSummaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: expenseDate) { newSummary.groupTotalSpending += expense.amount newSummary.totalPaidAmount += (expense.paidBy[member] ?? 0) newSummary.totalShare += totalSplitAmount newSummary.changeInBalance = (newSummary.totalPaidAmount - newSummary.totalShare) - newSummary.receivedAmount + newSummary.paidAmount - memberBalance[index].totalSummary[newSummaryIndex].summary = newSummary + memberBalance[index].balanceByCurrency[currency]?.totalSummary[newSummaryIndex].summary = newSummary } else { let summary = getInitialGroupSummaryFor(member: member, expense: expense) - memberBalance[index].totalSummary.append(summary) + memberBalance[index].balanceByCurrency[currency]?.totalSummary.append(summary) } case .Delete: - memberBalance[index].balance -= newSplitAmount + memberBalance[index].balanceByCurrency[currency]?.balance -= newSplitAmount - if var totalSummary = getLatestSummaryFrom(totalSummary: memberBalance[index].totalSummary, date: expenseDate)?.summary, - let summaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[index].totalSummary, date: expenseDate) { + let groupTotalSummary = memberBalance[index].balanceByCurrency[currency]?.totalSummary ?? [] + if var totalSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: expenseDate)?.summary, + let summaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: expenseDate) { totalSummary.groupTotalSpending -= expense.amount totalSummary.totalPaidAmount -= (expense.paidBy[member] ?? 0) totalSummary.totalShare -= totalSplitAmount totalSummary.changeInBalance = (totalSummary.totalPaidAmount - totalSummary.totalShare) - totalSummary.receivedAmount + totalSummary.paidAmount - memberBalance[index].totalSummary[summaryIndex].summary = totalSummary + memberBalance[index].balanceByCurrency[currency]?.totalSummary[summaryIndex].summary = totalSummary } } } else { // If the member doesn't have an existing entry, create a new one with the initial balance and summary let summary = getInitialGroupSummaryFor(member: member, expense: expense) - memberBalance.append(GroupMemberBalance(id: member, balance: newSplitAmount, totalSummary: [summary])) + memberBalance.append( + GroupMemberBalance(id: member, + balanceByCurrency: [currency: + GroupCurrencyBalance(balance: newSplitAmount, + totalSummary: [summary]) + ]) + ) } } @@ -214,6 +249,7 @@ public func getUpdatedMemberBalanceFor(transaction: Transactions, group: Groups, let amount = transaction.amount let payerId = transaction.payerId let receiverId = transaction.receiverId + let currency = transaction.currencyCode ?? Currency.defaultCurrency.code let currentYear = Calendar.current.component(.year, from: transactionDate) let currentMonth = Calendar.current.component(.month, from: transactionDate) @@ -222,21 +258,22 @@ public func getUpdatedMemberBalanceFor(transaction: Transactions, group: Groups, if let payerIndex = memberBalance.firstIndex(where: { $0.id == payerId }) { switch updateType { case .Add: - memberBalance[payerIndex].balance += amount + memberBalance[payerIndex].balanceByCurrency[currency]?.balance += amount // Check if there's an existing summary for this date - if var totalSummary = getLatestSummaryFrom(totalSummary: memberBalance[payerIndex].totalSummary, date: transactionDate)?.summary, - let summaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[payerIndex].totalSummary, date: transactionDate) { + let groupTotalSummary = memberBalance[payerIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if var totalSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: transactionDate)?.summary, + let summaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: transactionDate) { totalSummary.paidAmount += amount totalSummary.changeInBalance = (totalSummary.totalPaidAmount - totalSummary.totalShare) - totalSummary.receivedAmount + totalSummary.paidAmount - memberBalance[payerIndex].totalSummary[summaryIndex].summary = totalSummary + memberBalance[payerIndex].balanceByCurrency[currency]?.totalSummary[summaryIndex].summary = totalSummary } else { // If no summary exists, create a new one let memberSummary = GroupMemberSummary(groupTotalSpending: 0, totalPaidAmount: 0, totalShare: 0, paidAmount: amount, receivedAmount: 0, changeInBalance: amount) let totalSummary = GroupTotalSummary(year: currentYear, month: currentMonth, summary: memberSummary) - memberBalance[payerIndex].totalSummary.append(totalSummary) + memberBalance[payerIndex].balanceByCurrency[currency]?.totalSummary.append(totalSummary) } case .Update(let oldTransaction): @@ -246,41 +283,44 @@ public func getUpdatedMemberBalanceFor(transaction: Transactions, group: Groups, // Handle payer role switch: Update the old payer's balance and summary if let oldPayerIndex = memberBalance.firstIndex(where: { $0.id == oldPayerId }) { - memberBalance[oldPayerIndex].balance -= oldAmount - if let oldSummaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[oldPayerIndex].totalSummary, date: oldTransactionDate) { - var oldSummary = memberBalance[oldPayerIndex].totalSummary[oldSummaryIndex].summary + memberBalance[oldPayerIndex].balanceByCurrency[currency]?.balance -= oldAmount + let groupTotalSummary = memberBalance[oldPayerIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if let oldSummaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: oldTransactionDate) { + var oldSummary = groupTotalSummary[oldSummaryIndex].summary oldSummary.paidAmount -= oldAmount oldSummary.changeInBalance = (oldSummary.totalPaidAmount - oldSummary.totalShare) - oldSummary.receivedAmount + oldSummary.paidAmount - memberBalance[oldPayerIndex].totalSummary[oldSummaryIndex].summary = oldSummary + memberBalance[oldPayerIndex].balanceByCurrency[currency]?.totalSummary[oldSummaryIndex].summary = oldSummary } } // Update new payer's balance and summary for the new transaction with switched roles if let newPayerIndex = memberBalance.firstIndex(where: { $0.id == payerId }) { - memberBalance[newPayerIndex].balance += amount - if var newSummary = getLatestSummaryFrom(totalSummary: memberBalance[newPayerIndex].totalSummary, date: transactionDate)?.summary, - let newSummaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[newPayerIndex].totalSummary, date: transactionDate) { + memberBalance[newPayerIndex].balanceByCurrency[currency]?.balance += amount + let groupTotalSummary = memberBalance[newPayerIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if var newSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: transactionDate)?.summary, + let newSummaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: transactionDate) { newSummary.paidAmount += amount newSummary.changeInBalance = (newSummary.totalPaidAmount - newSummary.totalShare) - newSummary.receivedAmount + newSummary.paidAmount - memberBalance[newPayerIndex].totalSummary[newSummaryIndex].summary = newSummary + memberBalance[newPayerIndex].balanceByCurrency[currency]?.totalSummary[newSummaryIndex].summary = newSummary } else { // If no summary exists for the new date, create a new one let newMemberSummary = GroupMemberSummary(groupTotalSpending: 0, totalPaidAmount: 0, totalShare: 0, paidAmount: amount, receivedAmount: 0, changeInBalance: amount) let newTotalSummary = GroupTotalSummary(year: currentYear, month: currentMonth, summary: newMemberSummary) - memberBalance[newPayerIndex].totalSummary.append(newTotalSummary) + memberBalance[newPayerIndex].balanceByCurrency[currency]?.totalSummary.append(newTotalSummary) } } case .Delete: - memberBalance[payerIndex].balance -= amount + memberBalance[payerIndex].balanceByCurrency[currency]?.balance -= amount - if var totalSummary = getLatestSummaryFrom(totalSummary: memberBalance[payerIndex].totalSummary, date: transactionDate)?.summary, - let summaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[payerIndex].totalSummary, date: transactionDate) { + let groupTotalSummary = memberBalance[payerIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if var totalSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: transactionDate)?.summary, + let summaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: transactionDate) { totalSummary.paidAmount -= amount totalSummary.changeInBalance = (totalSummary.totalPaidAmount - totalSummary.totalShare - totalSummary.receivedAmount + totalSummary.paidAmount) - memberBalance[payerIndex].totalSummary[summaryIndex].summary = totalSummary + memberBalance[payerIndex].balanceByCurrency[currency]?.totalSummary[summaryIndex].summary = totalSummary } } } else { @@ -288,28 +328,33 @@ public func getUpdatedMemberBalanceFor(transaction: Transactions, group: Groups, let memberSummary = GroupMemberSummary(groupTotalSpending: 0, totalPaidAmount: 0, totalShare: 0, paidAmount: amount, receivedAmount: 0, changeInBalance: amount) let totalSummary = GroupTotalSummary(year: currentYear, month: currentMonth, summary: memberSummary) - memberBalance.append(GroupMemberBalance(id: payerId, balance: amount, totalSummary: [totalSummary])) + memberBalance.append(GroupMemberBalance(id: payerId, + balanceByCurrency: [ + currency: GroupCurrencyBalance(balance: amount, + totalSummary: [totalSummary]) + ])) } // For receiver if let receiverIndex = memberBalance.firstIndex(where: { $0.id == receiverId }) { switch updateType { case .Add: - memberBalance[receiverIndex].balance -= amount + memberBalance[receiverIndex].balanceByCurrency[currency]?.balance -= amount // Check if there's an existing summary for this date - if var totalSummary = getLatestSummaryFrom(totalSummary: memberBalance[receiverIndex].totalSummary, date: transactionDate)?.summary, - let summaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[receiverIndex].totalSummary, date: transactionDate) { + let groupTotalSummary = memberBalance[receiverIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if var totalSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: transactionDate)?.summary, + let summaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: transactionDate) { totalSummary.receivedAmount += amount totalSummary.changeInBalance = (totalSummary.totalPaidAmount - totalSummary.totalShare - totalSummary.receivedAmount + totalSummary.paidAmount) - memberBalance[receiverIndex].totalSummary[summaryIndex].summary = totalSummary + memberBalance[receiverIndex].balanceByCurrency[currency]?.totalSummary[summaryIndex].summary = totalSummary } else { // If no summary exists, create a new one let memberSummary = GroupMemberSummary(groupTotalSpending: 0, totalPaidAmount: 0, totalShare: 0, paidAmount: 0, receivedAmount: amount, changeInBalance: -amount) let totalSummary = GroupTotalSummary(year: currentYear, month: currentMonth, summary: memberSummary) - memberBalance[receiverIndex].totalSummary.append(totalSummary) + memberBalance[receiverIndex].balanceByCurrency[currency]?.totalSummary.append(totalSummary) } case .Update(let oldTransaction): @@ -319,41 +364,44 @@ public func getUpdatedMemberBalanceFor(transaction: Transactions, group: Groups, // Handle receiver role switch: Update the old receiver's balance and summary if let oldReceiverIndex = memberBalance.firstIndex(where: { $0.id == oldReceiverId }) { - memberBalance[oldReceiverIndex].balance += oldAmount - if let oldSummaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[oldReceiverIndex].totalSummary, date: oldTransactionDate) { - var oldSummary = memberBalance[oldReceiverIndex].totalSummary[oldSummaryIndex].summary + memberBalance[oldReceiverIndex].balanceByCurrency[currency]?.balance += oldAmount + let groupTotalSummary = memberBalance[oldReceiverIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if let oldSummaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: oldTransactionDate) { + var oldSummary = groupTotalSummary[oldSummaryIndex].summary oldSummary.receivedAmount -= oldAmount oldSummary.changeInBalance = (oldSummary.totalPaidAmount - oldSummary.totalShare) - oldSummary.receivedAmount + oldSummary.paidAmount - memberBalance[oldReceiverIndex].totalSummary[oldSummaryIndex].summary = oldSummary + memberBalance[oldReceiverIndex].balanceByCurrency[currency]?.totalSummary[oldSummaryIndex].summary = oldSummary } } // Update new receiver's balance and summary for the new transaction with switched roles if let newReceiverIndex = memberBalance.firstIndex(where: { $0.id == receiverId }) { - memberBalance[newReceiverIndex].balance -= amount - if var newSummary = getLatestSummaryFrom(totalSummary: memberBalance[newReceiverIndex].totalSummary, date: transactionDate)?.summary, - let newSummaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[newReceiverIndex].totalSummary, date: transactionDate) { + memberBalance[newReceiverIndex].balanceByCurrency[currency]?.balance -= amount + let groupTotalSummary = memberBalance[newReceiverIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if var newSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: transactionDate)?.summary, + let newSummaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: transactionDate) { newSummary.receivedAmount += amount newSummary.changeInBalance = (newSummary.totalPaidAmount - newSummary.totalShare) - newSummary.receivedAmount + newSummary.paidAmount - memberBalance[newReceiverIndex].totalSummary[newSummaryIndex].summary = newSummary + memberBalance[newReceiverIndex].balanceByCurrency[currency]?.totalSummary[newSummaryIndex].summary = newSummary } else { // If no summary exists for the new date, create a new one let newMemberSummary = GroupMemberSummary(groupTotalSpending: 0, totalPaidAmount: 0, totalShare: 0, paidAmount: 0, receivedAmount: amount, changeInBalance: -amount) let newTotalSummary = GroupTotalSummary(year: currentYear, month: currentMonth, summary: newMemberSummary) - memberBalance[newReceiverIndex].totalSummary.append(newTotalSummary) + memberBalance[newReceiverIndex].balanceByCurrency[currency]?.totalSummary.append(newTotalSummary) } } case .Delete: - memberBalance[receiverIndex].balance += amount + memberBalance[receiverIndex].balanceByCurrency[currency]?.balance += amount - if var totalSummary = getLatestSummaryFrom(totalSummary: memberBalance[receiverIndex].totalSummary, date: transactionDate)?.summary, - let summaryIndex = getLatestSummaryIndex(totalSummary: memberBalance[receiverIndex].totalSummary, date: transactionDate) { + let groupTotalSummary = memberBalance[receiverIndex].balanceByCurrency[currency]?.totalSummary ?? [] + if var totalSummary = getLatestSummaryFrom(totalSummary: groupTotalSummary, date: transactionDate)?.summary, + let summaryIndex = getLatestSummaryIndex(totalSummary: groupTotalSummary, date: transactionDate) { totalSummary.receivedAmount -= amount totalSummary.changeInBalance = (totalSummary.totalPaidAmount - totalSummary.totalShare - totalSummary.receivedAmount + totalSummary.paidAmount) - memberBalance[receiverIndex].totalSummary[summaryIndex].summary = totalSummary + memberBalance[receiverIndex].balanceByCurrency[currency]?.totalSummary[summaryIndex].summary = totalSummary } } } else { @@ -361,12 +409,16 @@ public func getUpdatedMemberBalanceFor(transaction: Transactions, group: Groups, let memberSummary = GroupMemberSummary(groupTotalSpending: 0, totalPaidAmount: 0, totalShare: 0, paidAmount: 0, receivedAmount: amount, changeInBalance: -amount) let totalSummary = GroupTotalSummary(year: currentYear, month: currentMonth, summary: memberSummary) - memberBalance.append(GroupMemberBalance(id: receiverId, balance: -amount, totalSummary: [totalSummary])) + memberBalance.append(GroupMemberBalance(id: receiverId, + balanceByCurrency: [ + currency: GroupCurrencyBalance(balance: -amount, + totalSummary: [totalSummary]) + ])) } let epsilon = 1e-10 - for i in 0.. [ let currentMonth = Calendar.current.component(.month, from: Date()) let currentYear = Calendar.current.component(.year, from: Date()) - return group.balances.first(where: { $0.id == userId })?.totalSummary.filter { + let currency = group.defaultCurrencyCode + let totalGroupSummary = group.balances.first(where: { $0.id == userId })?.balanceByCurrency[currency]?.totalSummary ?? [] + return totalGroupSummary.filter { $0.month == currentMonth && $0.year == currentYear - } ?? [] + } } diff --git a/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift b/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift index 69c8cf0f..6b1a57e9 100644 --- a/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift +++ b/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift @@ -102,7 +102,7 @@ class CreateGroupViewModel: BaseViewModel, ObservableObject { guard let userId = preference.user?.id else { return false } let localCurrency = Currency.getCurrentLocalCurrency().code - let memberBalance = GroupMemberBalance(id: userId, balance: 0, totalSummary: []) + let memberBalance = GroupMemberBalance(id: userId, balanceByCurrency: [:]) let group = Groups(name: groupName.trimming(spaces: .leadingAndTrailing), createdBy: userId, members: [userId], balances: [memberBalance], currencyCode: localCurrency) diff --git a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift index f1c2a8fc..ca342131 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift @@ -86,14 +86,15 @@ private struct GroupBalanceItemView: View { var body: some View { VStack(alignment: .leading, spacing: 20) { HStack(spacing: 16) { + let totalOwed = memberBalance.totalOwedAmount.reduce(0) { $0 + $1.value } HStack(spacing: 16) { MemberProfileImageView(imageUrl: imageUrl) - let hasDue = memberBalance.totalOwedAmount < 0 + let hasDue = totalOwed < 0 let name = viewModel.getMemberName(id: memberBalance.id, needFullName: true) let owesOrGetsBack = hasDue ? (memberBalance.id == preference.user?.id ? "owe" : "owes") : (memberBalance.id == preference.user?.id ? "get back" : "gets back") - if memberBalance.totalOwedAmount == 0 { + if totalOwed == 0 { Group { Text(name) .font(.subTitle2()) @@ -108,7 +109,7 @@ private struct GroupBalanceItemView: View { + Text(" \(owesOrGetsBack.localized) ") - + Text(memberBalance.totalOwedAmount.formattedCurrency) + + Text(totalOwed.formattedCurrency) .foregroundColor(hasDue ? errorColor : successColor) + Text(" in total") @@ -120,7 +121,7 @@ private struct GroupBalanceItemView: View { } .frame(maxWidth: .infinity, alignment: .leading) - if memberBalance.totalOwedAmount != 0 { + if totalOwed != 0 { ScrollToTopButton( icon: "chevron.down", iconColor: primaryText, bgColor: container2Color, showWithAnimation: true, size: (10, 7), isFirstGroupCell: memberBalance.isExpanded, @@ -152,7 +153,7 @@ private struct GroupBalanceItemMemberView: View { @Inject private var preference: SplitoPreference let id: String - let balances: [String: Double] + let balances: [String: [String: Double]] let viewModel: GroupBalancesViewModel @State private var showShareReminderSheet = false @@ -163,41 +164,42 @@ private struct GroupBalanceItemMemberView: View { HSpacer(32) VStack(alignment: .leading, spacing: 12) { - ForEach(balances.sorted(by: { $0.key < $1.key }), id: \.key) { (memberId, amount) in - let hasDue = amount < 0 - let imageUrl = viewModel.getMemberImage(id: memberId) - let owesMemberName = viewModel.getMemberName(id: hasDue ? memberId : id) - let owedMemberName = viewModel.getMemberName(id: hasDue ? id : memberId) - let owesText = ((hasDue ? id : memberId) == preference.user?.id) ? "owe" : "owes" - - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .center, spacing: 16) { - MemberProfileImageView(imageUrl: imageUrl, height: SUB_IMAGE_HEIGHT, scaleEffect: 0.6) - - Group { - Text("\(owedMemberName.capitalized) \(owesText.localized) ") - - + Text(amount.formattedCurrency) - .foregroundColor(hasDue ? errorColor : successColor) - - + Text(" to \(owesMemberName)") + ForEach(balances.sorted(by: { $0.key < $1.key }), id: \.key) { (currency, memberBalances) in + ForEach(memberBalances.sorted(by: { $0.key < $1.key }), id: \.key) { (memberId, amount) in + let hasDue = amount < 0 + let imageUrl = viewModel.getMemberImage(id: memberId) + let owesMemberName = viewModel.getMemberName(id: hasDue ? memberId : id) + let owedMemberName = viewModel.getMemberName(id: hasDue ? id : memberId) + let owesText = ((hasDue ? id : memberId) == preference.user?.id) ? "owe" : "owes" + + VStack(alignment: .leading, spacing: 8) { + HStack(alignment: .center, spacing: 16) { + MemberProfileImageView(imageUrl: imageUrl, height: SUB_IMAGE_HEIGHT, scaleEffect: 0.6) + + Group { + Text("\(owedMemberName.capitalized) \(owesText.localized) ") + + Text(amount.formattedCurrency) + .foregroundColor(hasDue ? errorColor : successColor) + + Text(" in \(currency.localized) to \(owesMemberName)") + } + .font(.body3()) + .foregroundStyle(disableText) } - .font(.body3()) - .foregroundStyle(disableText) - } - 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) - } - ) + 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) + } + ) + } } } } diff --git a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift index 27111766..665c67ab 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift @@ -72,29 +72,38 @@ class GroupBalancesViewModel: BaseViewModel, ObservableObject { let filteredBalances = group.balances.filter { group.members.contains($0.id) } - let memberBalances = filteredBalances.map { - MembersCombinedBalance(id: $0.id, totalOwedAmount: $0.balance) - } - - // Create group member balances for settlements - let groupMemberBalances = filteredBalances.map { - GroupMemberBalance(id: $0.id, balance: $0.balance, totalSummary: $0.totalSummary) + let memberBalances = filteredBalances.flatMap { balance in + balance.balanceByCurrency.map { (currency, balanceDetails) in + // Create a combined balance for each currency + MembersCombinedBalance(id: balance.id, totalOwedAmount: [currency: balanceDetails.balance], + balances: [currency: [balance.id: balanceDetails.balance]]) + } } // Calculate settlements between group members - let settlements = calculateSettlements(balances: groupMemberBalances) + let settlements = calculateSettlements(balances: filteredBalances) // Merge settlements with member balances let combinedBalances = settlements.reduce(into: memberBalances) { balances, settlement in - let senderIndex = balances.firstIndex { $0.id == settlement.sender } - let receiverIndex = balances.firstIndex { $0.id == settlement.receiver } - - if let senderIndex = senderIndex { - balances[senderIndex].balances[settlement.receiver, default: 0.0] -= settlement.amount - } - - if let receiverIndex = receiverIndex { - balances[receiverIndex].balances[settlement.sender, default: 0.0] += settlement.amount + // Find sender and receiver indices in the balances list + if let senderIndex = balances.firstIndex(where: { $0.id == settlement.sender }), + let receiverIndex = balances.firstIndex(where: { $0.id == settlement.receiver }) { + + // Handle sender's balance update + if balances[senderIndex].balances[settlement.currency] != nil { + // If currency balance exists for sender, subtract the settlement amount + balances[senderIndex].balances[settlement.currency]?[settlement.receiver, default: 0.0] -= settlement.amount + } else { + // If no balance exists, initialize the currency balance for the sender + balances[senderIndex].balances[settlement.currency] = [settlement.receiver: -settlement.amount] + } + + // Handle receiver's balance update + if balances[receiverIndex].balances[settlement.currency] != nil { + balances[receiverIndex].balances[settlement.currency]?[settlement.sender, default: 0.0] += settlement.amount + } else { + balances[receiverIndex].balances[settlement.currency] = [settlement.sender: settlement.amount] + } } } @@ -111,7 +120,7 @@ class GroupBalancesViewModel: BaseViewModel, ObservableObject { var sortedMembers = memberBalances var userBalance = sortedMembers.remove(at: userIndex) - userBalance.isExpanded = userBalance.totalOwedAmount != 0 + userBalance.isExpanded = userBalance.totalOwedAmount.values.reduce(0, +) != 0 sortedMembers.insert(userBalance, at: 0) sortedMembers.sort { member1, member2 in @@ -172,8 +181,8 @@ class GroupBalancesViewModel: BaseViewModel, ObservableObject { struct MembersCombinedBalance { let id: String var isExpanded: Bool = false - var totalOwedAmount: Double = 0 - var balances: [String: Double] = [:] + var totalOwedAmount: [String: Double] = [:] + var balances: [String: [String: Double]] = [:] } // MARK: - View States diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpView.swift index b17ffe3e..7d419a3d 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpView.swift @@ -88,18 +88,18 @@ private struct GroupMembersListView: View { return name1 < name2 } - ForEach(sortedMembers, id: \.key) { memberId, owingAmount in - if let member = viewModel.getMemberDataBy(id: memberId) { - GroupMemberCellView(member: member, amount: owingAmount) - .onTouchGesture { - viewModel.onMemberTap(memberId: member.id, amount: owingAmount) - } - - Divider() - .frame(height: 1) - .background(dividerColor) - } - } +// ForEach(sortedMembers, id: \.key) { memberId, owingAmount in +// if let member = viewModel.getMemberDataBy(id: memberId) { +// GroupMemberCellView(member: member, amount: owingAmount) +// .onTouchGesture { +// viewModel.onMemberTap(memberId: member.id, amount: owingAmount) +// } +// +// Divider() +// .frame(height: 1) +// .background(dividerColor) +// } +// } } } } diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift index fcddc8fd..7151d2b3 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift @@ -14,7 +14,7 @@ class GroupSettleUpViewModel: BaseViewModel, ObservableObject { @Inject private var groupRepository: GroupRepository @Published private(set) var viewState: ViewState = .loading - @Published private(set) var memberOwingAmount: [String: Double] = [:] + @Published private(set) var memberOwingAmount: [String: [String: Double]] = [:] /// [currencyCode: [memberId: balance]] private var group: Groups? private var members: [AppUser] = [] @@ -76,17 +76,28 @@ class GroupSettleUpViewModel: BaseViewModel, ObservableObject { } // MARK: - Helper Methods - func getMembersBalance(memberId: String) -> Double { + func getMembersBalance(memberId: String) -> [String: Double] { guard let group else { LogE("GroupSettingViewModel: \(#function) group not found.") - return 0 + return [:] } - if let index = group.balances.firstIndex(where: { $0.id == memberId }) { - return group.balances[index].balance + guard let memberBalance = group.balances.first(where: { $0.id == memberId })?.balanceByCurrency else { + LogE("GroupSettingViewModel: \(#function) Member's balance not found from balances.") + return [:] } - return 0 + var filteredBalances: [String: Double] = [:] + for (currency, balanceInfo) in memberBalance { + if balanceInfo.balance == 0 { continue } + filteredBalances[currency] = balanceInfo.balance + } + + if filteredBalances.isEmpty { // If no non-zero balances, fallback to original data + return memberBalance.mapValues { $0.balance } + } + + return [:] } func getMemberDataBy(id: String) -> AppUser? { diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift index a484e91b..f2f07318 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift @@ -93,7 +93,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { private func fetchGroup() async { do { group = try await groupRepository.fetchGroupBy(id: groupId) - selectedCurrency = Currency.getCurrencyFromCode(group?.defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(group?.defaultCurrencyCode) LogD("GroupPaymentViewModel: \(#function) Group fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch group \(groupId): \(error).") @@ -111,7 +111,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { paymentNote = transaction?.note ?? "" paymentImageUrl = transaction?.imageUrl paymentReason = transaction?.reason ?? "" - selectedCurrency = Currency.getCurrencyFromCode(transaction?.currencyCode ?? group?.defaultCurrency) + selectedCurrency = Currency.getCurrencyFromCode(transaction?.currencyCode ?? group?.defaultCurrencyCode) LogD("GroupPaymentViewModel: \(#function) Payment fetched successfully.") } catch { LogE("GroupPaymentViewModel: \(#function) Failed to fetch payment \(transactionId): \(error).") diff --git a/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift index 23376fb6..5b3b459b 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift @@ -66,7 +66,8 @@ class GroupTotalsViewModel: BaseViewModel, ObservableObject { case .thisYear: summaries = getTotalSummaryForCurrentYear() case .all: - summaries = group.balances.first(where: { $0.id == userId })?.totalSummary ?? [] + let groupBalance = group.balances.first(where: { $0.id == userId })?.balanceByCurrency ?? [:] + summaries = groupBalance["INR"]?.totalSummary ?? [] } summaryData = GroupMemberSummary( @@ -85,7 +86,8 @@ class GroupTotalsViewModel: BaseViewModel, ObservableObject { return [] } let currentYear = Calendar.current.component(.year, from: Date()) - return group.balances.first(where: { $0.id == user.id })?.totalSummary.filter { + let groupBalance = group.balances.first(where: { $0.id == user.id })?.balanceByCurrency ?? [:] + return groupBalance["INR"]?.totalSummary.filter { $0.year == currentYear } ?? [] } diff --git a/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingView.swift b/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingView.swift index a25c2b05..fbbf0e41 100644 --- a/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingView.swift +++ b/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingView.swift @@ -128,7 +128,7 @@ private struct GroupMembersView: View { LazyVStack(spacing: 20) { ForEach(viewModel.members) { member in let balance = viewModel.getMembersBalance(memberId: member.id) - GroupMemberCellView(member: member, amount: balance, + GroupMemberCellView(member: member, balance: balance, isAdmin: member.id == viewModel.group?.createdBy) .onTouchGesture { viewModel.handleMemberTap(member: member) @@ -201,7 +201,7 @@ private struct GroupMemberCellView: View { @Inject var preference: SplitoPreference let member: AppUser - let amount: Double + let balance: [String: Double] let isAdmin: Bool private var userName: String { @@ -247,22 +247,27 @@ private struct GroupMemberCellView: View { Spacer() - let isBorrowed = amount < 0 - VStack(alignment: .trailing, spacing: 4) { - if amount == 0 { - Text("settled up") - .font(.caption1()) - .foregroundStyle(disableText) - } else { - Text(isBorrowed ? "owes" : "gets back") - .font(.caption1()) - - Text(amount.formattedCurrency) - .font(.body1()) + if let firstBalance = balance.first { + let currency = firstBalance.key + let amount = firstBalance.value + let isBorrowed = amount < 0 + VStack(alignment: .trailing, spacing: 4) { + if amount == 0 { + Text("settled up") + .font(.caption1()) + .foregroundStyle(disableText) + } else { + Text(isBorrowed ? "owes" : "gets back") + .font(.caption1()) + + let currencySymbol = Currency.getCurrencyFromCode(currency).symbol + Text("\(currencySymbol) \(amount.formattedCurrency)") + .font(.body1()) + } } + .lineLimit(1) + .foregroundStyle(isBorrowed ? errorColor : successColor) } - .lineLimit(1) - .foregroundStyle(isBorrowed ? errorColor : successColor) } } } diff --git a/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift b/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift index 493514ef..f856053c 100644 --- a/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift @@ -75,17 +75,28 @@ class GroupSettingViewModel: BaseViewModel, ObservableObject { // MARK: - Helper Methods - func getMembersBalance(memberId: String) -> Double { + func getMembersBalance(memberId: String) -> [String: Double] { guard let group else { LogE("GroupSettingViewModel: \(#function) group not found.") - return 0 + return [:] } - if let index = group.balances.firstIndex(where: { $0.id == memberId }) { - return group.balances[index].balance + guard let memberBalance = group.balances.first(where: { $0.id == memberId })?.balanceByCurrency else { + LogE("GroupSettingViewModel: \(#function) Member's balance not found from balances.") + return [:] } - return 0 + var filteredBalances: [String: Double] = [:] + for (currency, balanceInfo) in memberBalance { + if balanceInfo.balance == 0 { continue } + filteredBalances[currency] = balanceInfo.balance + } + + if filteredBalances.isEmpty { // If no non-zero balances, fallback to original data + return memberBalance.mapValues { $0.balance } + } + + return [:] } func sortGroupMembers(members: [AppUser]) { @@ -157,7 +168,7 @@ class GroupSettingViewModel: BaseViewModel, ObservableObject { private func showRemoveMemberAlert(member: AppUser) { let memberBalance = getMembersBalance(memberId: member.id) - guard memberBalance == 0 else { + guard memberBalance.values.reduce(0, +) == 0 else { memberRemoveType = .remove showDebtOutstandingAlert(memberId: member.id) return @@ -172,7 +183,7 @@ class GroupSettingViewModel: BaseViewModel, ObservableObject { private func showLeaveGroupAlert(member: AppUser) { let memberBalance = getMembersBalance(memberId: member.id) - guard memberBalance == 0 else { + guard memberBalance.values.reduce(0, +) == 0 else { memberRemoveType = .leave showDebtOutstandingAlert(memberId: member.id) return diff --git a/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift b/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift index 202df87b..651e009d 100644 --- a/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift +++ b/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift @@ -255,7 +255,7 @@ private struct GroupExpenseHeaderView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - if viewModel.overallOwingAmount == 0 { + if viewModel.overallOwingAmount.values.reduce(0, +) == 0 { VStack(alignment: .center, spacing: 16) { Image(.tickmarkIcon) .resizable() @@ -278,10 +278,12 @@ private struct GroupExpenseHeaderView: View { .background(dividerColor) VStack(alignment: .leading, spacing: 12) { - ForEach(viewModel.memberOwingAmount.sorted(by: { $0.key < $1.key }), id: \.key) { (memberId, amount) in - let name = viewModel.getMemberDataBy(id: memberId)?.nameWithLastInitial ?? "Unknown" - GroupExpenseMemberOweView(name: name, amount: amount, - handleSimplifyInfoSheet: viewModel.handleSimplifyInfoSheet) + ForEach(viewModel.memberOwingAmount.sorted(by: { $0.key < $1.key }), id: \.key) { (_, memberOweAmounts) in + ForEach(memberOweAmounts.sorted(by: { $0.key < $1.key }), id: \.key) { (memberId, amount) in + let name = viewModel.getMemberDataBy(id: memberId)?.nameWithLastInitial ?? "Unknown" + GroupExpenseMemberOweView(name: name, amount: amount, + handleSimplifyInfoSheet: viewModel.handleSimplifyInfoSheet) + } } }.padding(16) } @@ -299,14 +301,15 @@ private struct GroupExpenseHeaderOverallView: View { var body: some View { HStack(alignment: .center, spacing: 0) { - let isDue = viewModel.overallOwingAmount < 0 + let (owedText, owedAmounts) = calculateOwedText(from: viewModel.overallOwingAmount) + let isDue = viewModel.overallOwingAmount.values.reduce(0, +) < 0 VStack(alignment: .leading, spacing: 4) { - Text("You \(isDue ? "owe overall" : "are overall owed")") + Text(owedText) .font(.body3()) .foregroundStyle(disableText) - Text("\(abs(viewModel.overallOwingAmount).formattedCurrency)") + Text(owedAmounts) .font(.body1()) .foregroundStyle(isDue ? errorColor : successColor) } @@ -331,6 +334,22 @@ private struct GroupExpenseHeaderOverallView: View { .padding(16) } } + + /// Helper function to calculate owed/owed text and amounts + private func calculateOwedText(from balances: [String: Double]) -> (String, String) { + // Separate positive and negative balances + let owedAmounts = balances.filter { $0.value < 0 } + .map { "\($0.key) \(abs($0.value).formattedCurrency)" } + .joined(separator: " + ") + let owedText = !owedAmounts.isEmpty ? "You owe \(owedAmounts)" : "You are owed" + + let owedByYou = balances.filter { $0.value > 0 } + .map { "\($0.key) \(abs($0.value).formattedCurrency)" } + .joined(separator: " + ") + + let messagePrefix = balances.values.reduce(0, +) < 0 ? "are owed" : "owe" + return ("You \(messagePrefix) ", "\(owedAmounts)") + } } private struct GroupExpenseMemberOweView: View { diff --git a/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift b/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift index 4bc711d5..4563e32e 100644 --- a/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift +++ b/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift @@ -20,8 +20,8 @@ class GroupHomeViewModel: BaseViewModel, ObservableObject { @Inject private var transactionRepository: TransactionRepository @Published private(set) var groupId: String - @Published private(set) var overallOwingAmount: Double = 0.0 @Published private(set) var currentMonthSpending: Double = 0.0 + @Published private(set) var overallOwingAmount: [String: Double] = [:] @Published var group: Groups? @Published var groupState: GroupState = .loading @@ -29,8 +29,8 @@ class GroupHomeViewModel: BaseViewModel, ObservableObject { @Published var expenses: [Expense] = [] @Published var expensesWithUser: [ExpenseWithUser] = [] @Published var transactionsCount: Int = 0 - @Published private(set) var memberOwingAmount: [String: Double] = [:] @Published private(set) var groupExpenses: [String: [ExpenseWithUser]] = [:] + @Published private(set) var memberOwingAmount: [String: [String: Double]] = [:] @Published var showSettleUpSheet = false @Published var showBalancesSheet = false @@ -280,7 +280,10 @@ class GroupHomeViewModel: BaseViewModel, ObservableObject { memberOwingAmount = Splito.calculateExpensesSimplified(userId: userId, memberBalances: group.balances) withAnimation(.easeOut) { - overallOwingAmount = group.balances.first(where: { $0.id == userId })?.balance ?? 0.0 + let balance = group.balances.first(where: { $0.id == userId })?.balanceByCurrency ?? [:] + for (currency, groupBalance) in balance { + overallOwingAmount[currency] = groupBalance.balance + } setGroupViewState() } } diff --git a/Splito/UI/Home/Groups/GroupListViewModel.swift b/Splito/UI/Home/Groups/GroupListViewModel.swift index 2a7c063b..077b40c2 100644 --- a/Splito/UI/Home/Groups/GroupListViewModel.swift +++ b/Splito/UI/Home/Groups/GroupListViewModel.swift @@ -49,11 +49,15 @@ class GroupListViewModel: BaseViewModel, ObservableObject { case .all: return searchedGroup.isEmpty ? combinedGroups : combinedGroups.filter { $0.group.name.localizedCaseInsensitiveContains(searchedGroup) } case .settled: - return searchedGroup.isEmpty ? combinedGroups.filter { $0.userBalance == 0 } : combinedGroups.filter { $0.userBalance == 0 && - $0.group.name.localizedCaseInsensitiveContains(searchedGroup) } + return searchedGroup.isEmpty + ? combinedGroups.filter { $0.userBalance.values.reduce(0, +) == 0 } + : combinedGroups.filter { $0.userBalance.values.reduce(0, +) == 0 && + $0.group.name.localizedCaseInsensitiveContains(searchedGroup) } case .unsettled: - return searchedGroup.isEmpty ? combinedGroups.filter { $0.userBalance != 0 } : combinedGroups.filter { $0.userBalance != 0 && - $0.group.name.localizedCaseInsensitiveContains(searchedGroup) } + return searchedGroup.isEmpty + ? combinedGroups.filter { $0.userBalance.values.reduce(0, +) != 0 } + : combinedGroups.filter { $0.userBalance.values.reduce(0, +) != 0 && + $0.group.name.localizedCaseInsensitiveContains(searchedGroup) } } } @@ -238,11 +242,16 @@ class GroupListViewModel: BaseViewModel, ObservableObject { return groupMembers } - private func getMembersBalance(group: Groups, memberId: String) -> Double { + private func getMembersBalance(group: Groups, memberId: String) -> [String: Double] { + var memberBalance: [String: Double] = [:] if let index = group.balances.firstIndex(where: { $0.id == memberId }) { - return group.balances[index].balance + let groupMemberBalance = group.balances[index].balanceByCurrency + for (currency, groupBalance) in groupMemberBalance { + memberBalance[currency] = groupBalance.balance + } + return memberBalance } - return 0 + return [:] } private func fetchGroup(groupId: String) async -> Groups? { @@ -292,8 +301,8 @@ extension GroupListViewModel { func handleSearchBarTap() { if (combinedGroups.isEmpty) || - (selectedTab == .unsettled && combinedGroups.filter({ $0.userBalance != 0 }).isEmpty) || - (selectedTab == .settled && combinedGroups.filter({ $0.userBalance == 0 }).isEmpty) { + (selectedTab == .unsettled && combinedGroups.filter({ $0.userBalance.values.reduce(0, +) != 0 }).isEmpty) || + (selectedTab == .settled && combinedGroups.filter({ $0.userBalance.values.reduce(0, +) == 0 }).isEmpty) { showToastFor(toast: .init(type: .info, title: "No groups yet", message: "There are no groups available to search.")) } else { withAnimation { @@ -314,8 +323,8 @@ extension GroupListViewModel { func handleTabItemSelection(_ selection: GroupListTabType) { guard case .hasGroup = groupListState else { return } - let settledGroups = combinedGroups.filter { $0.userBalance == 0 } - let unsettledGroups = combinedGroups.filter { $0.userBalance != 0 } + let settledGroups = combinedGroups.filter { $0.userBalance.values.reduce(0, +) == 0 } + let unsettledGroups = combinedGroups.filter { $0.userBalance.values.reduce(0, +) != 0 } withAnimation(.easeInOut(duration: 0.3)) { selectedTab = selection diff --git a/Splito/UI/Home/Groups/GroupListWithDetailView.swift b/Splito/UI/Home/Groups/GroupListWithDetailView.swift index ebd0268e..07115607 100644 --- a/Splito/UI/Home/Groups/GroupListWithDetailView.swift +++ b/Splito/UI/Home/Groups/GroupListWithDetailView.swift @@ -86,7 +86,7 @@ private struct GroupListCellView: View { self.isLastGroup = isLastGroup self.group = group self.viewModel = viewModel - self._showInfo = State(initialValue: isFirstGroup && group.userBalance != 0) + self._showInfo = State(initialValue: isFirstGroup && group.userBalance.values.reduce(0, +) != 0) } var body: some View { @@ -106,9 +106,11 @@ private struct GroupListCellView: View { Spacer(minLength: 8) + let defaultCurrency = group.group.defaultCurrencyCode + let userBalance = group.userBalance[defaultCurrency] ?? group.userBalance.first?.value ?? 0 VStack(alignment: .trailing, spacing: 0) { - let isBorrowed = group.userBalance < 0 - if group.userBalance == 0 { + let isBorrowed = group.userBalance.values.reduce(0, +) < 0 + if group.userBalance.values.reduce(0, +) == 0 { Text(group.group.hasExpenses ? "settled up" : "no expense") .font(.caption1()) .foregroundStyle(disableText) @@ -116,14 +118,17 @@ private struct GroupListCellView: View { } else { Text(isBorrowed ? "you owe" : "you are owed") .font(.caption1()) - Text(group.userBalance.formattedCurrency) + + let currency = group.userBalance[defaultCurrency] == nil ? group.userBalance.first?.key : defaultCurrency + let currencySymbol = Currency.getCurrencyFromCode(currency).symbol + Text("\(currencySymbol) \(userBalance.formattedCurrency)") .font(.body1()) } } .lineLimit(1) - .foregroundStyle(group.userBalance < 0 ? errorColor : successColor) + .foregroundStyle(group.userBalance.values.reduce(0, +) < 0 ? errorColor : successColor) - if group.userBalance != 0 { + if userBalance != 0 { GroupExpandBtnView(showInfo: $showInfo, isFirstGroup: isFirstGroup) } } @@ -134,9 +139,11 @@ private struct GroupListCellView: View { HSpacer(56) // width of image size for padding VStack(alignment: .leading, spacing: 8) { - ForEach(group.memberOweAmount.sorted(by: { $0.key < $1.key }), id: \.key) { (memberId, amount) in - let name = viewModel.getMemberData(from: group.members, of: memberId)?.nameWithLastInitial ?? "Unknown" - GroupExpenseMemberOweView(name: name, amount: amount) + ForEach(group.memberOweAmount.sorted(by: { $0.key < $1.key }), id: \.key) { (_, memberOweAmount) in + ForEach(memberOweAmount.sorted(by: { $0.key < $1.key }), id: \.key) { (memberId, amount) in + let name = viewModel.getMemberData(from: group.members, of: memberId)?.nameWithLastInitial ?? "Unknown" + GroupExpenseMemberOweView(name: name, amount: amount) + } } } .padding(.horizontal, 16)