diff --git a/HabitRPG/Extensions/Date-Extensions.swift b/HabitRPG/Extensions/Date-Extensions.swift index b067edced..253bc96de 100644 --- a/HabitRPG/Extensions/Date-Extensions.swift +++ b/HabitRPG/Extensions/Date-Extensions.swift @@ -37,4 +37,30 @@ func getShortRemainingString() -> String { } return string } + + func getImpreciseRemainingString() -> String { + let diff = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date(), to: self) + if let days = diff.day, days > 0 { + if days == 1 { + return L10n._1Day + } else { + return L10n.xDays(days) + } + } + if let hours = diff.hour, hours > 0 { + if hours == 1 { + return L10n._1Hour + } else { + return L10n.xHours(hours) + } + } + if let minutes = diff.day, minutes > 0 { + if minutes == 1 { + return L10n._1Minute + } else { + return L10n.xMinutes(minutes) + } + } + return L10n._1Minute + } } diff --git a/HabitRPG/Generated/Strings.swift b/HabitRPG/Generated/Strings.swift index 3d473698d..7676c5499 100644 --- a/HabitRPG/Generated/Strings.swift +++ b/HabitRPG/Generated/Strings.swift @@ -8,8 +8,14 @@ public enum L10n { /// Update bundle if you need to change app language static var bundle: Bundle? + /// 1 Day + public static var _1Day: String { return L10n.tr("Mainstrings", "1_day") } + /// 1 Hour + public static var _1Hour: String { return L10n.tr("Mainstrings", "1_hour") } /// 1 Hourglass public static var _1Hourglass: String { return L10n.tr("Mainstrings", "1_hourglass") } + /// 1 Minute + public static var _1Minute: String { return L10n.tr("Mainstrings", "1_minute") } /// Abort public static var abort: String { return L10n.tr("Mainstrings", "abort") } /// About @@ -878,6 +884,8 @@ public enum L10n { public static var reasonForReport: String { return L10n.tr("Mainstrings", "reason_for_report") } /// Recipient public static var recipient: String { return L10n.tr("Mainstrings", "recipient") } + /// Refresh for new Items + public static var refreshForItems: String { return L10n.tr("Mainstrings", "refresh_for_items") } /// Reject public static var reject: String { return L10n.tr("Mainstrings", "reject") } /// Remember to check off your Dailies! @@ -1080,6 +1088,10 @@ public enum L10n { public static var summary: String { return L10n.tr("Mainstrings", "summary") } /// Sunday public static var sunday: String { return L10n.tr("Mainstrings", "sunday") } + /// Swaps in %@ + public static func swapsInX(_ p1: String) -> String { + return L10n.tr("Mainstrings", "swaps_in_x", p1) + } /// Tags public static var tags: String { return L10n.tr("Mainstrings", "tags") } /// Take me back @@ -1232,6 +1244,10 @@ public enum L10n { public static func writeTo(_ p1: String) -> String { return L10n.tr("Mainstrings", "write_to", p1) } + /// %d Days + public static func xDays(_ p1: Int) -> String { + return L10n.tr("Mainstrings", "x_days", p1) + } /// %ld Filters public static func xFilters(_ p1: Int) -> String { return L10n.tr("Mainstrings", "x_filters", p1) @@ -1248,10 +1264,18 @@ public enum L10n { public static func xHourglasses(_ p1: Int) -> String { return L10n.tr("Mainstrings", "x_hourglasses", p1) } + /// %d Hours + public static func xHours(_ p1: Int) -> String { + return L10n.tr("Mainstrings", "x_hours", p1) + } /// %d Items pending public static func xItemsFound(_ p1: Int) -> String { return L10n.tr("Mainstrings", "x_items_found", p1) } + /// %d Minutes + public static func xMinutes(_ p1: Int) -> String { + return L10n.tr("Mainstrings", "x_minutes", p1) + } /// %d Months public static func xMonths(_ p1: Int) -> String { return L10n.tr("Mainstrings", "x_months", p1) diff --git a/HabitRPG/HRPGShopSectionHeaderCollectionReusableView.swift b/HabitRPG/HRPGShopSectionHeaderCollectionReusableView.swift index e56462d61..2e175c861 100644 --- a/HabitRPG/HRPGShopSectionHeaderCollectionReusableView.swift +++ b/HabitRPG/HRPGShopSectionHeaderCollectionReusableView.swift @@ -14,6 +14,7 @@ class HRPGShopSectionHeaderCollectionReusableView: UICollectionReusableView { @IBOutlet weak var gearCategoryLabel: UILabel! @IBOutlet weak var dropdownIconView: UIImageView! @IBOutlet weak var otherClassDisclaimer: UILabel! + @IBOutlet weak var swapsInLabel: UILabel! var onGearCategoryLabelTapped: (() -> Void)? diff --git a/HabitRPG/Habitica-Info.plist b/HabitRPG/Habitica-Info.plist index cf8e25e76..70200d202 100644 --- a/HabitRPG/Habitica-Info.plist +++ b/HabitRPG/Habitica-Info.plist @@ -39,7 +39,7 @@ CFBundleVersion - 954 + 957 CustomDomain ${CUSTOM_DOMAIN} DisableSSL diff --git a/HabitRPG/Repositories/ConfigRepository.swift b/HabitRPG/Repositories/ConfigRepository.swift index c00dfe6c3..e74f6c8f4 100644 --- a/HabitRPG/Repositories/ConfigRepository.swift +++ b/HabitRPG/Repositories/ConfigRepository.swift @@ -304,6 +304,11 @@ class ConfigRepository: NSObject { @objc func bool(variable: ConfigVariable) -> Bool { + #if DEBUG + if testingLevel.isDeveloper { + return variable.defaultValue() + } + #endif return ConfigRepository.remoteConfig.configValue(forKey: variable.name()).boolValue } diff --git a/HabitRPG/Storyboards/Base.lproj/BuyModal.storyboard b/HabitRPG/Storyboards/Base.lproj/BuyModal.storyboard index cebf8cb24..50c7a478f 100644 --- a/HabitRPG/Storyboards/Base.lproj/BuyModal.storyboard +++ b/HabitRPG/Storyboards/Base.lproj/BuyModal.storyboard @@ -1,9 +1,9 @@ - + - + @@ -20,14 +20,14 @@ - - + + - + - + @@ -56,7 +56,7 @@ - + - + - + @@ -136,7 +136,7 @@ - + @@ -168,9 +168,9 @@ - + - + @@ -200,7 +200,7 @@ - + @@ -212,7 +212,6 @@ - diff --git a/HabitRPG/Storyboards/Base.lproj/Shop.storyboard b/HabitRPG/Storyboards/Base.lproj/Shop.storyboard index d85afcedb..b80050204 100644 --- a/HabitRPG/Storyboards/Base.lproj/Shop.storyboard +++ b/HabitRPG/Storyboards/Base.lproj/Shop.storyboard @@ -77,6 +77,13 @@ + @@ -133,7 +140,9 @@ + + @@ -164,6 +173,7 @@ + diff --git a/HabitRPG/Strings/Base.lproj/Mainstrings.strings b/HabitRPG/Strings/Base.lproj/Mainstrings.strings index ae0c3257c..a10ef4300 100644 --- a/HabitRPG/Strings/Base.lproj/Mainstrings.strings +++ b/HabitRPG/Strings/Base.lproj/Mainstrings.strings @@ -1335,3 +1335,12 @@ "challenge_category.self_improvement" = "Self-Improvement"; "challenge_category.spirituality" = "Spirituality"; "challenge_category.time_management" = "Time-Management + Accountability"; + +"x_days" = "%d Days"; +"1_day" = "1 Day"; +"x_hours" = "%d Hours"; +"1_hour" = "1 Hour"; +"x_minutes" = "%d Minutes"; +"1_minute" = "1 Minute"; +"swaps_in_x" = "Switches in %@"; +"refresh_for_items" = "Refresh for new Items"; diff --git a/HabitRPG/TableViewDataSources/BaseReactiveDataSource.swift b/HabitRPG/TableViewDataSources/BaseReactiveDataSource.swift index e82de6b13..8887aef22 100644 --- a/HabitRPG/TableViewDataSources/BaseReactiveDataSource.swift +++ b/HabitRPG/TableViewDataSources/BaseReactiveDataSource.swift @@ -23,6 +23,7 @@ class ItemSection { var isHidden = false var showIfEmpty = false var items = [MODEL]() + var endDate: Date? = nil var isVisible: Bool { return !isHidden && (items.isEmpty == false || showIfEmpty) diff --git a/HabitRPG/TableViewDataSources/ShopCollectionViewDataSource.swift b/HabitRPG/TableViewDataSources/ShopCollectionViewDataSource.swift index 4c32745d3..b5a398c0b 100644 --- a/HabitRPG/TableViewDataSources/ShopCollectionViewDataSource.swift +++ b/HabitRPG/TableViewDataSources/ShopCollectionViewDataSource.swift @@ -121,6 +121,7 @@ class ShopCollectionViewDataSource: BaseReactiveCollectionViewDataSource(title: category.text) newSection.items = category.items newSection.key = category.identifier + newSection.endDate = category.endDate sections.append(newSection) } collectionView?.reloadData() @@ -214,6 +215,18 @@ class ShopCollectionViewDataSource: BaseReactiveCollectionViewDataSource Date() { + headerView.swapsInLabel.text = L10n.swapsInX(endDate.getImpreciseRemainingString()) + } else { + headerView.swapsInLabel.text = L10n.refreshForItems + } + headerView.swapsInLabel.textColor = ThemeService.shared.theme.tintedMainText + } else { + headerView.swapsInLabel.isHidden = true + } headerView.titleLabel.text = titleFor(section: indexPath.section)?.localizedUppercase } headerView.titleLabel.textColor = ThemeService.shared.theme.ternaryTextColor diff --git a/HabitRPG/TableviewCells/InAppRewardCell.swift b/HabitRPG/TableviewCells/InAppRewardCell.swift index a234d5836..2dc41501e 100644 --- a/HabitRPG/TableviewCells/InAppRewardCell.swift +++ b/HabitRPG/TableviewCells/InAppRewardCell.swift @@ -104,7 +104,7 @@ class InAppRewardCell: UICollectionViewCell { var currency: Currency? let price = reward.value currencyView.amount = Int(reward.value) - imageName = reward.imageName ?? "" + imageName = reward.iconName itemName = reward.text ?? "" if let currencyString = reward.currency, let thisCurrency = Currency(rawValue: currencyString) { currencyView.currency = thisCurrency @@ -121,7 +121,7 @@ class InAppRewardCell: UICollectionViewCell { showPurchaseConfirmation() } - if let date = reward.eventEnd { + if let date = reward.availableUntil() { availableUntil = date } else { availableUntil = nil diff --git a/HabitRPG/UI/General/MainMenuViewController.swift b/HabitRPG/UI/General/MainMenuViewController.swift index 484bdb0db..98c2cb8d8 100644 --- a/HabitRPG/UI/General/MainMenuViewController.swift +++ b/HabitRPG/UI/General/MainMenuViewController.swift @@ -389,7 +389,7 @@ class MainMenuViewController: BaseTableViewController { }) } - if configRepository.bool(variable: .enableCustomizationShop) { + if configRepository.bool(variable: .enableCustomizationShop) || configRepository.testingLevel.isDeveloper { menuItem(withKey: .customizationShop).isHidden = false tableView.reloadData() } diff --git a/HabitRPG/UI/Inventory/AvatarDetailViewController.swift b/HabitRPG/UI/Inventory/AvatarDetailViewController.swift index cb52ade4f..c58f6fd00 100644 --- a/HabitRPG/UI/Inventory/AvatarDetailViewController.swift +++ b/HabitRPG/UI/Inventory/AvatarDetailViewController.swift @@ -27,7 +27,7 @@ class AvatarDetailViewController: BaseCollectionViewController, UICollectionView super.viewDidLoad() topHeaderCoordinator?.followScrollView = false - newCustomizationLayout = configRepository.bool(variable: .enableCustomizationShop) + newCustomizationLayout = configRepository.bool(variable: .enableCustomizationShop) || configRepository.testingLevel.isDeveloper if let type = customizationType { if type == "eyewear" || type == "headAccessory" || type == "back" || type == "animalTails" { @@ -101,7 +101,7 @@ class AvatarDetailViewController: BaseCollectionViewController, UICollectionView } private func showPurchaseDialog(customization: CustomizationProtocol, withSource sourceView: UIView?) { - let sheet = HostingBottomSheetController(rootView: BottomSheetMenu(Text(customization.titleText), iconName: customization.imageName(forUserPreferences: nil) ?? "", menuItems: { + let sheet = HostingBottomSheetController(rootView: BottomSheetMenu(Text(customization.titleText), iconName: customization.iconName(forUserPreferences: nil) ?? "", menuItems: { BottomSheetMenuitem(title: HStack { Text(L10n.purchaseForWithoutCurrency(Int(customization.price))) Image(uiImage: HabiticaIcons.imageOfGem) diff --git a/HabitRPG/UI/Inventory/AvatarOverviewViewController.swift b/HabitRPG/UI/Inventory/AvatarOverviewViewController.swift index 202d8e85e..6cbef37bf 100644 --- a/HabitRPG/UI/Inventory/AvatarOverviewViewController.swift +++ b/HabitRPG/UI/Inventory/AvatarOverviewViewController.swift @@ -111,47 +111,47 @@ class AvatarOverviewViewController: BaseUIViewController, UIScrollViewDelegate { bodySizeControl.selectedSegmentIndex = user.preferences?.size == "slim" ? 0 : 1 if let shirt = user.preferences?.shirt { - shirtView.configure("Icon_\(user.preferences?.size ?? "slim")_shirt_\(shirt)") + shirtView.configure("icon_\(user.preferences?.size ?? "slim")_shirt_\(shirt)") } if let skin = user.preferences?.skin { - skinView.configure("Icon_skin_\(skin)") + skinView.configure("icon_skin_\(skin)") } if let hairColor = user.preferences?.hair?.color { let bangs = user.preferences?.hair?.bangs - hairColorView.configure("Icon_hair_bangs_\(bangs ?? 1)_\(hairColor)") + hairColorView.configure("icon_hair_bangs_\(bangs ?? 1)_\(hairColor)") if bangs != 0 { - hairBangsView.configure("Icon_hair_bangs_\(bangs ?? 1)_\(hairColor)") + hairBangsView.configure("icon_hair_bangs_\(bangs ?? 1)_\(hairColor)") } else { hairBangsView.configure(nil) } if let base = user.preferences?.hair?.base, base != 0 { - hairBaseView.configure("Icon_hair_base_\(base)_\(hairColor)") + hairBaseView.configure("icon_hair_base_\(base)_\(hairColor)") } else { hairBaseView.configure(nil) } if let beard = user.preferences?.hair?.beard, beard != 0 { - hairBeardView.configure("Icon_hair_beard_\(beard)_\(hairColor)") + hairBeardView.configure("icon_hair_beard_\(beard)_\(hairColor)") } else { hairBeardView.configure(nil) } if let mustache = user.preferences?.hair?.mustache, mustache != 0 { - hairMustacheView.configure("Icon_hair_mustache_\(mustache)_\(hairColor)") + hairMustacheView.configure("icon_hair_mustache_\(mustache)_\(hairColor)") } else { hairMustacheView.configure(nil) } } if let flower = user.preferences?.hair?.flower, flower != 0 { - hairFlowerView.configure("Icon_hair_flower_\(flower)") + hairFlowerView.configure("icon_hair_flower_\(flower)") } else { hairFlowerView.configure(nil) } if let chair = user.preferences?.chair, chair != "none" { - wheelchairView.configure("Icon_chair_\(chair)") + wheelchairView.configure("icon_chair_\(chair)") } else { wheelchairView.configure(nil) } diff --git a/HabitRPG/Views/AvatarShopItemView.swift b/HabitRPG/Views/AvatarShopItemView.swift index 74ed1b934..3adbd3fab 100644 --- a/HabitRPG/Views/AvatarShopItemView.swift +++ b/HabitRPG/Views/AvatarShopItemView.swift @@ -22,6 +22,8 @@ class AvatarShopItemView: UIView { private var user: UserProtocol? + private var reward: InAppRewardProtocol? + @IBInspectable var shouldHideNotes: Bool { get { return shopItemDescriptionLabel.isHidden @@ -60,18 +62,7 @@ class AvatarShopItemView: UIView { setupView() } - init(withReward reward: InAppRewardProtocol, withUser user: UserProtocol?, for contentView: UIView) { - super.init(frame: contentView.bounds) - setupView() - self.user = user - - shopItemTitleLabel.text = reward.text - - var purchaseType = "" - if let date = reward.eventEnd { - setAvailableUntil(date: date) - } - + private func buildSwapDict(reward: InAppRewardProtocol, user: AvatarProtocol?) -> [String: String] { let layerKey: String if reward.path?.contains("skin") == true { layerKey = "skin" @@ -92,10 +83,44 @@ class AvatarShopItemView: UIView { } else { layerKey = "" } - avatarView.swappedDict = [ + var swappedDict = [ layerKey: reward.imageName?.replacingOccurrences(of: "icon_", with: "") ?? "" ] + if reward.path?.contains("color") == true { + if let hair = user?.preferences?.hair, let color = reward.key?.split(separator: "_").last { + if hair.bangs > 0 { + swappedDict["hair-bangs"] = "hair_bangs_\(hair.bangs)_\(color)" + } + if hair.base > 0 { + swappedDict["hair-base"] = "hair_base_\(hair.base)_\(color)" + } + if hair.beard > 0 { + swappedDict["hair-beard"] = "hair_beard_\(hair.beard)_\(color)" + } + if hair.mustache > 0 { + swappedDict["hair-mustache"] = "hair_mustache_\(hair.mustache)_\(color)" + } + } + } + return swappedDict + } + + init(withReward reward: InAppRewardProtocol, withUser user: UserProtocol?, for contentView: UIView) { + super.init(frame: contentView.bounds) + self.reward = reward + setupView() + self.user = user + + shopItemTitleLabel.text = reward.text + + var purchaseType = "" + if let date = reward.availableUntil() { + setAvailableUntil(date: date) + } + + avatarView.swappedDict = buildSwapDict(reward: reward, user: user) + if let user = user { avatarView.avatar = AvatarViewModel(avatar: user) } @@ -124,7 +149,7 @@ class AvatarShopItemView: UIView { topBannerLabel.textColor = .white topBannerLabel.text = lockedReason topBannerWrapper.isHidden = false - } else { + } else if reward.availableUntil() == nil { topBannerWrapper.isHidden = true } } @@ -134,6 +159,9 @@ class AvatarShopItemView: UIView { } func setAvatar(_ avatar: AvatarProtocol) { + if let reward = self.reward { + avatarView.swappedDict = buildSwapDict(reward: reward, user: avatar) + } avatarView.avatar = AvatarViewModel(avatar: avatar) } diff --git a/HabitRPG/Views/AvatarShopItemView.xib b/HabitRPG/Views/AvatarShopItemView.xib index 23133113a..bf60e770b 100644 --- a/HabitRPG/Views/AvatarShopItemView.xib +++ b/HabitRPG/Views/AvatarShopItemView.xib @@ -24,14 +24,14 @@ - - + + - + - - + + + + + + + + + + + + - - + + +