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 @@
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+