From 4e6fd906685e592cbd8b4b55ecd8f43f4576a648 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Mon, 22 Dec 2025 19:19:28 +0800 Subject: [PATCH] 1 --- Shared/KBAPI.h | 3 + keyBoard.xcodeproj/project.pbxproj | 20 +- .../My/my_chongzhi_bg.imageset/Contents.json | 22 ++ .../my_chongzhi_bg@2x.png | Bin 0 -> 7328 bytes .../my_chongzhi_bg@3x.png | Bin 0 -> 14218 bytes .../My/my_record_icon.imageset/Contents.json | 22 ++ .../my_record_icon@2x.png | Bin 0 -> 2637 bytes .../my_record_icon@3x.png | Bin 0 -> 5055 bytes keyBoard/Class/Me/M/KBConsumptionRecord.h | 25 ++ keyBoard/Class/Me/M/KBConsumptionRecord.m | 16 + keyBoard/Class/Me/V/KBConsumptionRecordCell.h | 16 + keyBoard/Class/Me/V/KBConsumptionRecordCell.m | 123 +++++++ keyBoard/Class/Me/VC/KBConsumptionRecordVC.h | 14 + keyBoard/Class/Me/VC/KBConsumptionRecordVC.m | 337 ++++++++++++++++++ keyBoard/Class/Me/VC/MyVC.m | 8 +- keyBoard/Class/Me/VM/KBMyVM.h | 6 + keyBoard/Class/Me/VM/KBMyVM.m | 41 +++ keyBoard/Class/Shop/VC/KBSkinDetailVC.m | 5 +- keyBoard/Class/Shop/VM/KBShopVM.h | 6 + keyBoard/Class/Shop/VM/KBShopVM.m | 18 + 20 files changed, 679 insertions(+), 3 deletions(-) create mode 100644 keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@2x.png create mode 100644 keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@3x.png create mode 100644 keyBoard/Assets.xcassets/My/my_record_icon.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@2x.png create mode 100644 keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@3x.png create mode 100644 keyBoard/Class/Me/M/KBConsumptionRecord.h create mode 100644 keyBoard/Class/Me/M/KBConsumptionRecord.m create mode 100644 keyBoard/Class/Me/V/KBConsumptionRecordCell.h create mode 100644 keyBoard/Class/Me/V/KBConsumptionRecordCell.m create mode 100644 keyBoard/Class/Me/VC/KBConsumptionRecordVC.h create mode 100644 keyBoard/Class/Me/VC/KBConsumptionRecordVC.m diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index eee74cb..6334536 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -57,6 +57,9 @@ #define API_THEME_RECOMMENDED @"/themes/recommended" // 推荐主题列表 #define API_THEME_SEARCH @"/themes/search" // 搜索主题(themeName) #define API_USER_THEMES_BATCH_DELETE @"/user-themes/batch-delete" // 批量删除用户主题 +#define API_THEME_PURCHASE_LIST @"/themes/purchase/list" // 查询主题购买记录 +#define API_THEME_RESTORE @"/themes/restore" // 恢复已删除的主题 +#define API_WALLET_TRANSACTIONS @"/wallet/transactions" // 分页查询钱包交易记录 /// pay #define API_VALIDATE_RECEIPT @"/apple/validate-receipt" // 排行榜标签列表 diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index b605a9a..bca6d1f 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -115,6 +115,9 @@ 04890A052EC0BBBB00FABA60 /* KBCategoryTitleImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04890A032EC0BBBB00FABA60 /* KBCategoryTitleImageView.m */; }; 04890B122EC2F00000FABA60 /* KBMyHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04890B112EC2F00000FABA60 /* KBMyHeaderView.m */; }; 0498BD622EDFFC12006CC1D5 /* KBMyVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD612EDFFC12006CC1D5 /* KBMyVM.m */; }; + A1F0C1B12F1234567890ABCD /* KBConsumptionRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1A12F1234567890ABCD /* KBConsumptionRecord.m */; }; + A1F0C1B22F1234567890ABCD /* KBConsumptionRecordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1A32F1234567890ABCD /* KBConsumptionRecordCell.m */; }; + A1F0C1B32F1234567890ABCD /* KBConsumptionRecordVC.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */; }; 0498BD652EE0116D006CC1D5 /* KBEmailLoginVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD642EE0116D006CC1D5 /* KBEmailLoginVC.m */; }; 0498BD682EE01180006CC1D5 /* KBEmailRegistVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD672EE01180006CC1D5 /* KBEmailRegistVC.m */; }; 0498BD6B2EE025FC006CC1D5 /* KBForgetPwdVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD6A2EE025FC006CC1D5 /* KBForgetPwdVC.m */; }; @@ -401,9 +404,15 @@ 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchThemeModel.m; sourceTree = ""; }; 048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = ""; }; 048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = ""; }; - 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = ""; }; + 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = ""; }; 048908E12EBF821700FABA60 /* KBSkinDetailVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailVC.h; sourceTree = ""; }; 048908E22EBF760000FABA60 /* MySkinCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinCell.m; sourceTree = ""; }; + A1F0C1A02F1234567890ABCD /* KBConsumptionRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConsumptionRecord.h; sourceTree = ""; }; + A1F0C1A12F1234567890ABCD /* KBConsumptionRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBConsumptionRecord.m; sourceTree = ""; }; + A1F0C1A22F1234567890ABCD /* KBConsumptionRecordCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConsumptionRecordCell.h; sourceTree = ""; }; + A1F0C1A32F1234567890ABCD /* KBConsumptionRecordCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBConsumptionRecordCell.m; sourceTree = ""; }; + A1F0C1A42F1234567890ABCD /* KBConsumptionRecordVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConsumptionRecordVC.h; sourceTree = ""; }; + A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBConsumptionRecordVC.m; sourceTree = ""; }; 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailVC.m; sourceTree = ""; }; 048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailTagCell.h; sourceTree = ""; }; 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailTagCell.m; sourceTree = ""; }; @@ -1335,6 +1344,8 @@ 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */, 7ECBD0E320F971D0FBEDD7BC /* KBMyTheme.h */, 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */, + A1F0C1A02F1234567890ABCD /* KBConsumptionRecord.h */, + A1F0C1A12F1234567890ABCD /* KBConsumptionRecord.m */, ); path = M; sourceTree = ""; @@ -1346,6 +1357,8 @@ 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */, 048908E12EBF760000FABA60 /* MySkinCell.h */, 048908E22EBF760000FABA60 /* MySkinCell.m */, + A1F0C1A22F1234567890ABCD /* KBConsumptionRecordCell.h */, + A1F0C1A32F1234567890ABCD /* KBConsumptionRecordCell.m */, 048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */, 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */, 048908E72EBF843000FABA60 /* KBSkinDetailHeaderCell.h */, @@ -1379,6 +1392,8 @@ 049FB2192EC20A9E00FAB05D /* KBMyKeyBoardVC.m */, 048908DE2EBF73DC00FABA60 /* MySkinVC.h */, 048908DF2EBF73DC00FABA60 /* MySkinVC.m */, + A1F0C1A42F1234567890ABCD /* KBConsumptionRecordVC.h */, + A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */, 049FB2212EC311F900FAB05D /* KBPersonInfoVC.h */, 049FB2222EC311F900FAB05D /* KBPersonInfoVC.m */, 04791F902ED48010004E8522 /* KBNoticeVC.h */, @@ -1990,6 +2005,9 @@ 048908BC2EBE1FCB00FABA60 /* BaseViewController.m in Sources */, 0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */, 471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */, + A1F0C1B12F1234567890ABCD /* KBConsumptionRecord.m in Sources */, + A1F0C1B22F1234567890ABCD /* KBConsumptionRecordCell.m in Sources */, + A1F0C1B32F1234567890ABCD /* KBConsumptionRecordVC.m in Sources */, 04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */, 0498BD712EE02A41006CC1D5 /* KBForgetPwdNewPwdVC.m in Sources */, 048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */, diff --git a/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/Contents.json b/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/Contents.json new file mode 100644 index 0000000..e9854fc --- /dev/null +++ b/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "my_chongzhi_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "my_chongzhi_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@2x.png b/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..86f27f9dc4b9d7d68513fc6fd98ddf09a9a1c4df GIT binary patch literal 7328 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 agUNF5f9_+P{|fyCMp8^pv`oYx;Qs*i5fHEd literal 0 HcmV?d00001 diff --git a/keyBoard/Assets.xcassets/My/my_record_icon.imageset/Contents.json b/keyBoard/Assets.xcassets/My/my_record_icon.imageset/Contents.json new file mode 100644 index 0000000..5da8e0b --- /dev/null +++ b/keyBoard/Assets.xcassets/My/my_record_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "my_record_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "my_record_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@2x.png b/keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cd5a79304ccff495bc1ab7d01f26886c9475ac58 GIT binary patch literal 2637 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 Vp^n@xD>MKA002ovPDHLkV1k5c{a641 literal 0 HcmV?d00001 diff --git a/keyBoard/Class/Me/M/KBConsumptionRecord.h b/keyBoard/Class/Me/M/KBConsumptionRecord.h new file mode 100644 index 0000000..70c2ffd --- /dev/null +++ b/keyBoard/Class/Me/M/KBConsumptionRecord.h @@ -0,0 +1,25 @@ +// +// KBConsumptionRecord.h +// keyBoard +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 消费/购买记录模型 1 消费 2充值 +typedef NS_ENUM(NSInteger, KBConsumptionRecordType) { + KBConsumptionRecordTypeConsumption = 1, + KBConsumptionRecordTypeRecharge = 2, +}; + +@interface KBConsumptionRecord : NSObject +@property (nonatomic, strong, nullable) NSNumber *amount; +@property (nonatomic, assign) KBConsumptionRecordType type; +@property (nonatomic, strong, nullable) NSNumber *beforeBalance; +@property (nonatomic, strong, nullable) NSNumber *afterBalance; +@property (nonatomic, copy, nullable) NSString *kbdescription; +@property (nonatomic, copy, nullable) NSString *createdAt; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/M/KBConsumptionRecord.m b/keyBoard/Class/Me/M/KBConsumptionRecord.m new file mode 100644 index 0000000..e10d679 --- /dev/null +++ b/keyBoard/Class/Me/M/KBConsumptionRecord.m @@ -0,0 +1,16 @@ +// +// KBConsumptionRecord.m +// keyBoard +// + +#import "KBConsumptionRecord.h" + +@implementation KBConsumptionRecord ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + // JSON: { "id": 0, "tagName": "xxx" } + // Model: tagId / tagName + return @{ + @"kbdescription" : @"description", + }; +} +@end diff --git a/keyBoard/Class/Me/V/KBConsumptionRecordCell.h b/keyBoard/Class/Me/V/KBConsumptionRecordCell.h new file mode 100644 index 0000000..3d941f7 --- /dev/null +++ b/keyBoard/Class/Me/V/KBConsumptionRecordCell.h @@ -0,0 +1,16 @@ +// +// KBConsumptionRecordCell.h +// keyBoard +// + +#import "BaseCell.h" +@class KBConsumptionRecord; + +NS_ASSUME_NONNULL_BEGIN + +@interface KBConsumptionRecordCell : BaseCell ++ (NSString *)reuseId; +- (void)configWithRecord:(KBConsumptionRecord *)record; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/V/KBConsumptionRecordCell.m b/keyBoard/Class/Me/V/KBConsumptionRecordCell.m new file mode 100644 index 0000000..396eee3 --- /dev/null +++ b/keyBoard/Class/Me/V/KBConsumptionRecordCell.m @@ -0,0 +1,123 @@ +// +// KBConsumptionRecordCell.m +// keyBoard +// + +#import "KBConsumptionRecordCell.h" +#import "KBConsumptionRecord.h" +#import +#import "UIColor+Extension.h" + +@interface KBConsumptionRecordCell () +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) UILabel *timeLabel; +@property (nonatomic, strong) UILabel *amountLabel; +@property (nonatomic, strong) UIView *lineView; +@end + +@implementation KBConsumptionRecordCell + +- (void)setupUI { + self.contentView.backgroundColor = [UIColor whiteColor]; + [self.contentView addSubview:self.titleLabel]; + [self.contentView addSubview:self.timeLabel]; + [self.contentView addSubview:self.amountLabel]; + [self.contentView addSubview:self.lineView]; + + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(16); + make.top.equalTo(self.contentView).offset(14); + make.right.lessThanOrEqualTo(self.amountLabel.mas_left).offset(-12); + }]; + + [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.titleLabel); + make.top.equalTo(self.titleLabel.mas_bottom).offset(6); + make.bottom.equalTo(self.contentView).offset(-14); + make.right.lessThanOrEqualTo(self.amountLabel.mas_left).offset(-12); + }]; + + [self.amountLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.contentView).offset(-16); + make.centerY.equalTo(self.titleLabel); + }]; + + [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(16); + make.right.equalTo(self.contentView).offset(-16); + make.bottom.equalTo(self.contentView); + make.height.mas_equalTo(KB_ONE_PIXEL); + }]; +} + +- (void)configWithRecord:(KBConsumptionRecord *)record { + NSString *title = record.kbdescription.length ? record.kbdescription : KBLocalized(@"Consumption"); + self.titleLabel.text = title; + + self.timeLabel.text = record.createdAt.length ? record.createdAt : @"--"; + + NSString *displayAmount = [self kb_formatAmountText:record.amount]; + self.amountLabel.text = displayAmount; + + UIColor *incomeColor = [UIColor colorWithHex:0x66CD7C]; + UIColor *expenseColor = [UIColor colorWithHex:0xCD2853]; + self.amountLabel.textColor = (record.type == KBConsumptionRecordTypeRecharge) ? incomeColor : expenseColor; +} + +- (NSString *)kb_formatAmountText:(NSNumber *)amount { + if (![amount isKindOfClass:NSNumber.class]) { return @"--"; } + static NSNumberFormatter *formatter; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.minimumFractionDigits = 2; + formatter.maximumFractionDigits = 2; + formatter.minimumIntegerDigits = 1; + formatter.positivePrefix = @"+"; + }); + return [formatter stringFromNumber:amount] ?: @"--"; +} + +#pragma mark - Lazy + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [UILabel new]; + _titleLabel.font = [KBFont medium:15]; + _titleLabel.textColor = [UIColor colorWithHex:KBBlackValue]; + _titleLabel.text = KBLocalized(@"Consumption"); + } + return _titleLabel; +} + +- (UILabel *)timeLabel { + if (!_timeLabel) { + _timeLabel = [UILabel new]; + _timeLabel.font = [KBFont regular:12]; + _timeLabel.textColor = [UIColor colorWithHex:0x9FA5B5]; + _timeLabel.text = @"--"; + } + return _timeLabel; +} + +- (UILabel *)amountLabel { + if (!_amountLabel) { + _amountLabel = [UILabel new]; + _amountLabel.font = [KBFont medium:16]; + _amountLabel.textColor = [UIColor colorWithHex:0xE36464]; + _amountLabel.textAlignment = NSTextAlignmentRight; + _amountLabel.text = @"--"; + } + return _amountLabel; +} + +- (UIView *)lineView { + if (!_lineView) { + _lineView = [UIView new]; + _lineView.backgroundColor = [UIColor colorWithHex:0xEFEFF1]; + } + return _lineView; +} + +@end diff --git a/keyBoard/Class/Me/VC/KBConsumptionRecordVC.h b/keyBoard/Class/Me/VC/KBConsumptionRecordVC.h new file mode 100644 index 0000000..70bfdc7 --- /dev/null +++ b/keyBoard/Class/Me/VC/KBConsumptionRecordVC.h @@ -0,0 +1,14 @@ +// +// KBConsumptionRecordVC.h +// keyBoard +// + +#import "BaseViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface KBConsumptionRecordVC : BaseViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/VC/KBConsumptionRecordVC.m b/keyBoard/Class/Me/VC/KBConsumptionRecordVC.m new file mode 100644 index 0000000..403290b --- /dev/null +++ b/keyBoard/Class/Me/VC/KBConsumptionRecordVC.m @@ -0,0 +1,337 @@ +// +// KBConsumptionRecordVC.m +// keyBoard +// + +#import "KBConsumptionRecordVC.h" +#import "KBConsumptionRecord.h" +#import "KBConsumptionRecordCell.h" +#import "KBMyVM.h" +#import "KBShopVM.h" +#import "KBJfPay.h" +#import +#import "UIColor+Extension.h" +#import "KBHUD.h" +#import + +@interface KBConsumptionRecordVC () +@property (nonatomic, strong) BaseTableView *tableView; +@property (nonatomic, strong) UIView *headerView; +@property (nonatomic, strong) UIView *cardView; +@property (nonatomic, strong) UILabel *pointsTitleLabel; +@property (nonatomic, strong) UILabel *pointsLabel; +@property (nonatomic, strong) UIImageView *pointsIconView; +@property (nonatomic, strong) UIButton *rechargeButton; +@property (nonatomic, strong) UIImageView *sectionIconView; +@property (nonatomic, strong) UILabel *sectionTitleLabel; + +@property (nonatomic, strong) NSMutableArray *records; +@property (nonatomic, strong) KBMyVM *viewModel; +@property (nonatomic, strong) KBShopVM *shopVM; +@property (nonatomic, strong) UIImageView *bgImageView; // 全屏背景图 +@property (nonatomic, assign) NSInteger pageNumber; +@property (nonatomic, assign) NSInteger pageSize; +@property (nonatomic, assign) BOOL isLoading; + +@end + +@implementation KBConsumptionRecordVC + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + self.kb_titleLabel.text = KBLocalized(@"Consumption Record"); + self.kb_navView.backgroundColor = [UIColor clearColor]; + self.bgImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"my_bg_icon"]]; + self.bgImageView.contentMode = UIViewContentModeScaleAspectFill; + [self.view insertSubview:self.bgImageView belowSubview:self.kb_navView]; + [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.view); + }]; + self.records = [NSMutableArray array]; + [self.view addSubview:self.tableView]; + [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.bottom.equalTo(self.view); + make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT); + }]; + + self.tableView.tableHeaderView = self.headerView; + + self.pageNumber = 1; + self.pageSize = 10; + [self setupRefresh]; + [self fetchWalletBalance]; + [self.tableView.mj_header beginRefreshing]; +} + +#pragma mark - Data + +- (void)fetchWalletBalance { + __weak typeof(self) weakSelf = self; + [self.shopVM fetchWalletBalanceWithCompletion:^(NSString * _Nullable balance, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (!error && balance.length > 0) { + weakSelf.pointsLabel.text = balance; + } + }); + }]; +} + +- (void)setupRefresh { + __weak typeof(self) weakSelf = self; + self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ + [weakSelf refreshRecords]; + }]; + self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ + [weakSelf loadMoreRecords]; + }]; + self.tableView.mj_footer.hidden = YES; +} + +- (void)refreshRecords { + if (self.isLoading) { + [self.tableView.mj_header endRefreshing]; + return; + } + self.pageNumber = 1; + [self.tableView.mj_footer resetNoMoreData]; + [self fetchPurchaseRecordsIsRefresh:YES]; +} + +- (void)loadMoreRecords { + if (self.isLoading || self.tableView.mj_footer.state == MJRefreshStateNoMoreData) { + [self.tableView.mj_footer endRefreshing]; + return; + } + self.pageNumber += 1; + [self fetchPurchaseRecordsIsRefresh:NO]; +} + +- (void)fetchPurchaseRecordsIsRefresh:(BOOL)isRefresh { + self.isLoading = YES; + BOOL showHUD = !self.tableView.mj_header.isRefreshing && !self.tableView.mj_footer.isRefreshing; + if (showHUD) { + [KBHUD show]; + } + __weak typeof(self) weakSelf = self; + [self.viewModel fetchWalletTransactionsWithPage:self.pageNumber + pageSize:self.pageSize + completion:^(NSArray * _Nullable records, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + weakSelf.isLoading = NO; + if (showHUD) { + [KBHUD dismiss]; + } + if (isRefresh) { + [weakSelf.tableView.mj_header endRefreshing]; + } else { + [weakSelf.tableView.mj_footer endRefreshing]; + } + if (error) { + if (!isRefresh) { + weakSelf.pageNumber = MAX(1, weakSelf.pageNumber - 1); + } + NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error"); + [KBHUD showInfo:msg]; + return; + } + if (isRefresh) { + [weakSelf.records removeAllObjects]; + } + if (records.count > 0) { + [weakSelf.records addObjectsFromArray:records]; + } + [weakSelf.tableView reloadData]; + weakSelf.tableView.mj_footer.hidden = (weakSelf.records.count == 0); + if (records.count < weakSelf.pageSize) { + [weakSelf.tableView.mj_footer endRefreshingWithNoMoreData]; + } + }); + }]; +} + +#pragma mark - UITableView + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.records.count; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return KBFit(78); +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + KBConsumptionRecordCell *cell = [tableView dequeueReusableCellWithIdentifier:[KBConsumptionRecordCell reuseId] + forIndexPath:indexPath]; + if (indexPath.row < self.records.count) { + [cell configWithRecord:self.records[indexPath.row]]; + } + return cell; +} + +#pragma mark - Actions + +- (void)onRecharge { + KBJfPay *vc = [[KBJfPay alloc] init]; + [self.navigationController pushViewController:vc animated:YES]; +} + +#pragma mark - Lazy + +- (BaseTableView *)tableView { + if (!_tableView) { + _tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.backgroundColor = [UIColor clearColor]; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.dataSource = self; + _tableView.delegate = self; + [_tableView registerClass:KBConsumptionRecordCell.class forCellReuseIdentifier:[KBConsumptionRecordCell reuseId]]; + } + return _tableView; +} + +- (UIView *)headerView { + if (!_headerView) { + CGFloat headerHeight = KBFit(210); + _headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, headerHeight)]; + _headerView.backgroundColor = [UIColor clearColor]; + + [_headerView addSubview:self.cardView]; + [_headerView addSubview:self.sectionIconView]; + [_headerView addSubview:self.sectionTitleLabel]; + + [self.cardView addSubview:self.pointsTitleLabel]; + [self.cardView addSubview:self.pointsIconView]; + [self.cardView addSubview:self.pointsLabel]; + [self.cardView addSubview:self.rechargeButton]; + + [self.cardView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(_headerView).offset(16); + make.right.equalTo(_headerView).offset(-16); + make.top.equalTo(_headerView).offset(24); + make.height.mas_equalTo(KBFit(126)); + }]; + + [self.pointsTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(16); + make.top.equalTo(self.cardView).offset(18); + }]; + + [self.pointsIconView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(16); + make.centerY.equalTo(self.cardView); + make.width.height.mas_equalTo(38); + }]; + + [self.pointsLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.pointsIconView.mas_right).offset(8); + make.centerY.equalTo(self.pointsIconView).offset(5); + }]; + + [self.rechargeButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.cardView).offset(-16); + make.centerY.equalTo(self.pointsIconView); + make.width.mas_equalTo(114); + make.height.mas_equalTo(42); + }]; + + [self.sectionIconView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(_headerView).offset(16); + make.top.equalTo(self.cardView.mas_bottom).offset(18); + make.width.height.mas_equalTo(24); + }]; + + [self.sectionTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.sectionIconView); + make.left.equalTo(self.sectionIconView.mas_right).offset(8); + }]; + } + return _headerView; +} + +- (UIView *)cardView { + if (!_cardView) { + _cardView = [UIView new]; + _cardView.backgroundColor = [UIColor colorWithHex:0xC5FFF6]; + _cardView.layer.cornerRadius = 20; + _cardView.layer.masksToBounds = YES; + } + return _cardView; +} + +- (UILabel *)pointsTitleLabel { + if (!_pointsTitleLabel) { + _pointsTitleLabel = [UILabel new]; + _pointsTitleLabel.text = KBLocalized(@"My Points"); + _pointsTitleLabel.font = [KBFont medium:14]; + _pointsTitleLabel.textColor = [UIColor colorWithHex:0x6B7A7A]; + } + return _pointsTitleLabel; +} + +- (UILabel *)pointsLabel { + if (!_pointsLabel) { + _pointsLabel = [UILabel new]; + _pointsLabel.text = @"0"; + _pointsLabel.font = [KBFont bold:40]; + _pointsLabel.textColor = [UIColor colorWithHex:0x02BEAC]; + } + return _pointsLabel; +} + +- (UIImageView *)pointsIconView { + if (!_pointsIconView) { + _pointsIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]]; + _pointsIconView.contentMode = UIViewContentModeScaleAspectFit; + } + return _pointsIconView; +} + +- (UIButton *)rechargeButton { + if (!_rechargeButton) { + _rechargeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_rechargeButton setTitle:KBLocalized(@"Recharge") forState:UIControlStateNormal]; + _rechargeButton.titleLabel.font = [KBFont medium:13]; + [_rechargeButton setTitleColor:[UIColor colorWithHex:0x1B1F1A] forState:UIControlStateNormal]; +// _rechargeButton.backgroundColor = [UIColor colorWithHex:0xCFF7EA]; + [_rechargeButton setBackgroundImage:[UIImage imageNamed:@"my_chongzhi_bg"] forState:UIControlStateNormal]; + _rechargeButton.layer.cornerRadius = 21; + _rechargeButton.layer.masksToBounds = YES; + [_rechargeButton addTarget:self action:@selector(onRecharge) forControlEvents:UIControlEventTouchUpInside]; + } + return _rechargeButton; +} + +- (UIImageView *)sectionIconView { + if (!_sectionIconView) { + _sectionIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]]; + _sectionIconView.contentMode = UIViewContentModeScaleAspectFit; + } + return _sectionIconView; +} + +- (UILabel *)sectionTitleLabel { + if (!_sectionTitleLabel) { + _sectionTitleLabel = [UILabel new]; + _sectionTitleLabel.text = KBLocalized(@"Consumption Details"); + _sectionTitleLabel.font = [KBFont medium:14]; + _sectionTitleLabel.textColor = [UIColor colorWithHex:KBBlackValue]; + } + return _sectionTitleLabel; +} + +- (KBMyVM *)viewModel { + if (!_viewModel) { + _viewModel = [[KBMyVM alloc] init]; + } + return _viewModel; +} + +- (KBShopVM *)shopVM { + if (!_shopVM) { + _shopVM = [[KBShopVM alloc] init]; + } + return _shopVM; +} + +@end diff --git a/keyBoard/Class/Me/VC/MyVC.m b/keyBoard/Class/Me/VC/MyVC.m index 3b9cd2e..022b14d 100644 --- a/keyBoard/Class/Me/VC/MyVC.m +++ b/keyBoard/Class/Me/VC/MyVC.m @@ -14,6 +14,8 @@ #import "KBNoticeVC.h" #import "KBFeedBackVC.h" #import "KBMyVM.h" +#import "KBConsumptionRecordVC.h" + @interface MyVC () @property (nonatomic, strong) BaseTableView *tableView; // 列表 @@ -37,8 +39,9 @@ make.edges.equalTo(self.view); }]; - // 数据源(title + SF Symbols 名称 + 色值),分两组 + // 数据源(title + SF Symbols 名称 + 色值),分两组 my_record_icon self.data = @[ + @[@{ @"title": KBLocalized(@"Consumption record"), @"icon": @"my_record_icon", @"color": @(0x60A3FF),@"id":@"8" }], @[@{ @"title": KBLocalized(@"Notice"), @"icon": @"my_notice_icon", @"color": @(0x60A3FF),@"id":@"1" }], @[@{ @"title": KBLocalized(@"Share App"), @"icon": @"my_share_icon", @"color": @(0xF5A623),@"id":@"2" }], @[@{ @"title": KBLocalized(@"Feedback"), @"icon": @"my_feedback_icon", @"color": @(0xB06AFD),@"id":@"3" }, @@ -130,6 +133,9 @@ }else if ([itemID isEqualToString:@"6"]){ + }else if ([itemID isEqualToString:@"8"]){ + KBConsumptionRecordVC *vc = [[KBConsumptionRecordVC alloc] init]; + [self.navigationController pushViewController:vc animated:true]; }else if ([itemID isEqualToString:@"7"]){ KBTestVC *vc = [[KBTestVC alloc] init]; [self.navigationController pushViewController:vc animated:true]; diff --git a/keyBoard/Class/Me/VM/KBMyVM.h b/keyBoard/Class/Me/VM/KBMyVM.h index 1764f9c..25df2f6 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.h +++ b/keyBoard/Class/Me/VM/KBMyVM.h @@ -8,6 +8,7 @@ #import #import "KBCharacter.h" #import "KBMyTheme.h" +#import "KBConsumptionRecord.h" @class KBUser; NS_ASSUME_NONNULL_BEGIN @@ -26,6 +27,7 @@ typedef void(^KBDeleteUserCharacterCompletion)(BOOL success, NSError * _Nullable typedef void(^KBMyPurchasedThemesCompletion)(NSArray *_Nullable themes, NSError *_Nullable error); typedef void(^KBDeleteThemesCompletion)(BOOL success, NSError *_Nullable error); typedef void(^KBSubmitFeedbackCompletion)(BOOL success, NSError *_Nullable error); +typedef void(^KBMyPurchaseRecordCompletion)(NSArray *_Nullable records, NSError *_Nullable error); @interface KBMyVM : NSObject @@ -39,6 +41,10 @@ typedef void(^KBSubmitFeedbackCompletion)(BOOL success, NSError *_Nullable error /// 批量删除用户主题(/user-themes/batch-delete) - (void)deletePurchasedThemesWithIds:(NSArray *)themeIds completion:(KBDeleteThemesCompletion)completion; +/// 分页查询钱包交易记录(/wallet/transactions) +- (void)fetchWalletTransactionsWithPage:(NSInteger)pageNum + pageSize:(NSInteger)pageSize + completion:(KBMyPurchaseRecordCompletion)completion; /// 本地已下载主题列表 - (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion; /// 删除本地主题资源 diff --git a/keyBoard/Class/Me/VM/KBMyVM.m b/keyBoard/Class/Me/VM/KBMyVM.m index f15d7b7..102ea58 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.m +++ b/keyBoard/Class/Me/VM/KBMyVM.m @@ -158,6 +158,47 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo }]; } +- (void)fetchWalletTransactionsWithPage:(NSInteger)pageNum + pageSize:(NSInteger)pageSize + completion:(KBMyPurchaseRecordCompletion)completion { + NSInteger safePageNum = pageNum > 0 ? pageNum : 1; + NSInteger safePageSize = pageSize > 0 ? pageSize : 10; + NSDictionary *body = @{ + @"pageNum": @(safePageNum), + @"pageSize": @(safePageSize) + }; + [[KBNetworkManager shared] POST:API_WALLET_TRANSACTIONS + jsonBody:body + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary * _Nullable json, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + if (error) { + if (completion) completion(nil, error); + return; + } + id dataObj = json[KBData] ?: json[@"data"]; + if (![dataObj isKindOfClass:[NSDictionary class]]) { + NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain + code:KBNetworkErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}]; + if (completion) completion(nil, e); + return; + } + id recordsObj = [(NSDictionary *)dataObj objectForKey:@"records"]; + if (![recordsObj isKindOfClass:[NSArray class]]) { + NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain + code:KBNetworkErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}]; + if (completion) completion(nil, e); + return; + } + NSArray *records = [KBConsumptionRecord mj_objectArrayWithKeyValuesArray:(NSArray *)recordsObj]; + if (completion) completion(records, nil); + }]; +} + - (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion { dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ NSArray *records = [KBSkinInstallBridge installedSkinRecords]; diff --git a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m index 34f199e..e45660b 100644 --- a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m +++ b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m @@ -293,7 +293,10 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) { mode:KBSkinSourceModeRemoteZip completion:^(BOOL success) { if (success) { -// [KBHUD showSuccess:KBLocalized(@"已开始下载")]; + NSString *themeId = self.detailModel.themeId; + if (themeId.length > 0) { + [self.shopVM restoreThemeWithId:themeId completion:nil]; + } } else { [KBHUD showInfo:KBLocalized(@"下载失败")]; } diff --git a/keyBoard/Class/Shop/VM/KBShopVM.h b/keyBoard/Class/Shop/VM/KBShopVM.h index 8d24cd5..d1f19da 100644 --- a/keyBoard/Class/Shop/VM/KBShopVM.h +++ b/keyBoard/Class/Shop/VM/KBShopVM.h @@ -27,6 +27,8 @@ typedef void(^KBShopPurchaseCompletion)(BOOL success, NSError *_Nullable error); typedef void(^KBShopDownloadInfoCompletion)(NSDictionary *_Nullable info, NSError *_Nullable error); +typedef void(^KBShopRestoreCompletion)(BOOL success, + NSError *_Nullable error); @interface KBShopVM : NSObject @property (nonatomic, copy, readonly, nullable) NSArray *styles; @@ -56,6 +58,10 @@ typedef void(^KBShopDownloadInfoCompletion)(NSDictionary *_Nullable info, /// 推荐主题列表(用于皮肤详情页底部网格) - (void)fetchRecommendedThemesWithCompletion:(KBShopThemesCompletion)completion; +/// 恢复已删除的主题(/themes/restore) +- (void)restoreThemeWithId:(nullable NSString *)themeId + completion:(nullable KBShopRestoreCompletion)completion; + @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Shop/VM/KBShopVM.m b/keyBoard/Class/Shop/VM/KBShopVM.m index 98b6470..c64c118 100644 --- a/keyBoard/Class/Shop/VM/KBShopVM.m +++ b/keyBoard/Class/Shop/VM/KBShopVM.m @@ -201,6 +201,24 @@ }]; } +- (void)restoreThemeWithId:(nullable NSString *)themeId + completion:(nullable KBShopRestoreCompletion)completion { + if (themeId.length == 0) { + if (completion) completion(NO, [self kb_invalidParameterError]); + return; + } + NSDictionary *body = @{@"themeId": [self kb_themeIdParamFromString:themeId]}; + [[KBNetworkManager shared] POST:API_THEME_RESTORE + jsonBody:body + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary * _Nullable json, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + if (completion) completion(error == nil, error); + }]; +} + - (id)kb_themeIdParamFromString:(NSString *)themeId { if (themeId.length == 0) { return @""; } NSNumberFormatter *formatter = [NSNumberFormatter new];