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 0000000..86f27f9 Binary files /dev/null and b/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@2x.png differ diff --git a/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@3x.png b/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@3x.png new file mode 100644 index 0000000..94be0e5 Binary files /dev/null and b/keyBoard/Assets.xcassets/My/my_chongzhi_bg.imageset/my_chongzhi_bg@3x.png differ 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 0000000..cd5a793 Binary files /dev/null and b/keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@2x.png differ diff --git a/keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@3x.png b/keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@3x.png new file mode 100644 index 0000000..464aef9 Binary files /dev/null and b/keyBoard/Assets.xcassets/My/my_record_icon.imageset/my_record_icon@3x.png differ 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];