Skip to content

Commit

Permalink
feat: MykSuite dashboard (#1419)
Browse files Browse the repository at this point in the history
* chore: Switch to dashboard branch

* feat: New account cell in parameters to display MyKSuiteStore view

* feat(MyKSuiteStore): Add type to DI

* feat(MyKSuiteDashboardViewBridgeController): Reworked presentation

* feat(AccountManager): Refresh myKSuiteStore on login

* feat(ParameterTableViewController): Grouped cell UI update

* chore: Removed iOS 15 conditional code

* feat(): Modern sheet presentation for MyKSuiteBridgeViewController

* feat(presentUpSaleSheet): Animate a dismiss before presenting

* feat(ios-features): Added necessary libraries as a Framework

* chore(MyKSuiteDashboardViewBridgeController): Bumped to lattest ios-features API

* feat(ParameterTableViewController): Some i18n

* chore: PR Feedback

* chore: Remove conditional ios version dependent code

* feat(ParameterTableViewController): Dynamic sections if mykSuite enabled

* fix(ParameterTableViewCell): Proper prepare for reuse implementation

* feat(ParameterTableViewController): Inter18n general section

* fix: New ios-features build

* chore: Bump core-ui

* feat(AsyncImageView): A sample swUI view to display account image still requires fine tuning

* feat(AccountManager): Call to updateMyKSuite is no longer breaking authentification if broken

* fix(ParameterTableViewController): Correct rounded corner on cell

* feat(ParameterTableViewCell): Cell has support for other fonts and can display the current mykSuite account

* chore: Bump to ios-features 1.0.2

* chore: Branding my kSuite plus with a plus logo not a plus plus

* feat(ParameterTableViewController): Custom design table view section

* chore: Remove force display of myksuite

* feat(AsyncImageView): Loading view with default color

* feat: Check for mykSuite availlable for a user

* chore: Bump ios-features

* refactor(ParameterTableViewController): Better async loading

* fix(ParameterTableViewController): Section title is now correct without a mykSuite

* chore: Sonar

* refactor(ParameterTableViewController): Use a font with correct color directly

* refactor(MyKSuiteDashboardViewBridgeController): Simplify constructor

* feat(AccountManager): Update mykSuite on updateUser

* refactor(ParameterTableViewController): Remove extraneous lazy DI for direct resolution call

* refactor(MenuViewController): Remove extraneous import
  • Loading branch information
adrien-coye authored Feb 14, 2025
1 parent 6aac4cf commit 5fd647b
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 18 deletions.
10 changes: 5 additions & 5 deletions Tuist/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "88f7f532a7e7657a65b78bfcaf4a729503a5cbc882035f94e3ccc805e73a8845",
"originHash" : "8861f96269cb0948c6a4f36939cb053cc533985c8abc8a6cd517110b6da6bc0f",
"pins" : [
{
"identity" : "alamofire",
Expand Down Expand Up @@ -96,8 +96,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/ios-core-ui",
"state" : {
"revision" : "89873aff2c300f9f2966340b7b3e8832e88a95a6",
"version" : "18.0.1"
"revision" : "4d988c5e91d27db9ea3905242cb6c3a44df472d0",
"version" : "18.1.0"
}
},
{
Expand All @@ -114,8 +114,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/ios-features",
"state" : {
"revision" : "cf6608e2d84d8f921fd00806689a13ad4a7d96ff",
"version" : "1.0.1"
"revision" : "c1b1a15d0fd6bf4622dc6f0d675e795b60c952f1",
"version" : "1.0.3"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Tuist/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ let package = Package(
.package(url: "https://github.com/apple/swift-algorithms", .upToNextMajor(from: "1.2.0")),
.package(url: "https://github.com/Alamofire/Alamofire", .upToNextMajor(from: "5.2.2")),
.package(url: "https://github.com/Infomaniak/ios-core", .upToNextMajor(from: "15.0.0")),
.package(url: "https://github.com/Infomaniak/ios-core-ui", .upToNextMajor(from: "18.0.0")),
.package(url: "https://github.com/Infomaniak/ios-features", .upToNextMajor(from: "1.0.1")),
.package(url: "https://github.com/Infomaniak/ios-core-ui", .upToNextMajor(from: "18.1.0")),
.package(url: "https://github.com/Infomaniak/ios-features", .upToNextMajor(from: "1.0.3")),
.package(url: "https://github.com/Infomaniak/ios-login", .upToNextMajor(from: "7.2.0")),
.package(url: "https://github.com/Infomaniak/ios-dependency-injection", .upToNextMajor(from: "2.0.3")),
.package(url: "https://github.com/Infomaniak/swift-concurrency", .upToNextMajor(from: "1.0.0")),
Expand Down
192 changes: 181 additions & 11 deletions kDrive/UI/Controller/Menu/ParameterTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import InfomaniakCoreCommonUI
import InfomaniakCoreUIKit
import InfomaniakDI
import InfomaniakLogin
import kDriveCore
import kDriveResources
import MyKSuite
import Sentry
import UIKit

Expand All @@ -30,7 +33,46 @@ class ParameterTableViewController: BaseGroupedTableViewController {

let driveFileManager: DriveFileManager

private enum ParameterRow: CaseIterable {
lazy var packId = DrivePackId(rawValue: driveFileManager.drive.pack.name)

var mykSuiteEnabled: Bool = false

private enum ParameterSection: Int, CaseIterable {
case mykSuite
case general

func title(packId: DrivePackId?) -> String {
switch self {
case .mykSuite:
if packId == .myKSuite {
return "my kSuite"
} else if packId == .myKSuitePlus {
return "my kSuite+"
} else {
return ""
}
case .general:
return KDriveResourcesStrings.Localizable.settingsSectionGeneral
}
}
}

private enum MykSuiteParameterRow: CaseIterable {
case email
case mySubscription

var title: String {
switch self {
case .email:
@InjectService var accountManager: AccountManageable
return accountManager.currentAccount?.user.email ?? ""
case .mySubscription:
return MyKSuiteLocalizable.iosMyKSuiteDashboardSubscriptionButton
}
}
}

private enum GeneralParameterRow: CaseIterable {
case photos
case theme
case notifications
Expand Down Expand Up @@ -62,10 +104,6 @@ class ParameterTableViewController: BaseGroupedTableViewController {
}
}

private var tableContent: [ParameterRow] {
return ParameterRow.allCases
}

init(driveFileManager: DriveFileManager) {
self.driveFileManager = driveFileManager
super.init()
Expand All @@ -80,6 +118,7 @@ class ParameterTableViewController: BaseGroupedTableViewController {
tableView.register(cellView: ParameterWifiTableViewCell.self)

navigationItem.hideBackButtonText()
checkMykSuiteEnabledAndRefresh()
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -105,20 +144,86 @@ class ParameterTableViewController: BaseGroupedTableViewController {
}
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return nil
}

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let currentSection: ParameterSection?
if mykSuiteEnabled {
currentSection = ParameterSection(rawValue: section)
} else {
currentSection = ParameterSection.general
}

guard let currentSection else { return nil }

let headerView = UIView()
headerView.backgroundColor = .clear

let label = IKLabel()
label.text = currentSection.title(packId: packId)
label.font = TextStyle.header3.font

label.translatesAutoresizingMaskIntoConstraints = false
headerView.addSubview(label)

NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 24),
label.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -24),
label.centerYAnchor.constraint(equalTo: headerView.centerYAnchor)
])

return headerView
}

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
guard mykSuiteEnabled else {
return 1
}

return ParameterSection.allCases.count
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableContent.count
guard mykSuiteEnabled else {
return GeneralParameterRow.allCases.count
}

switch section {
case ParameterSection.mykSuite.rawValue:
return MykSuiteParameterRow.allCases.count
case ParameterSection.general.rawValue:
return GeneralParameterRow.allCases.count
default:
return 0
}
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = tableContent[indexPath.row]
guard mykSuiteEnabled else {
return generalCell(tableView, forRowAt: indexPath)
}

switch indexPath.section {
case ParameterSection.mykSuite.rawValue:
return mykSuiteCell(tableView, forRowAt: indexPath)
case ParameterSection.general.rawValue:
return generalCell(tableView, forRowAt: indexPath)
default:
fatalError("invalid indexPath: \(indexPath)")
}
}

private func generalCell(_ tableView: UITableView, forRowAt indexPath: IndexPath) -> UITableViewCell {
let row = GeneralParameterRow.allCases[indexPath.row]
switch row {
case .photos, .theme, .notifications:
let cell = tableView.dequeueReusableCell(type: ParameterTableViewCell.self, for: indexPath)
cell.initWithPositionAndShadow(isFirst: indexPath.row == 0, isLast: indexPath.row == tableContent.count - 1)
cell.initWithPositionAndShadow(
isFirst: indexPath.row == 0,
isLast: indexPath.row == GeneralParameterRow.allCases.count - 1
)
cell.titleLabel.text = row.title
if row == .photos {
cell.valueLabel.text = photoLibraryUploader.isSyncEnabled ? KDriveResourcesStrings.Localizable
Expand All @@ -140,16 +245,65 @@ class ParameterTableViewController: BaseGroupedTableViewController {
return cell
case .security, .storage, .about, .deleteAccount:
let cell = tableView.dequeueReusableCell(type: ParameterAboutTableViewCell.self, for: indexPath)
cell.initWithPositionAndShadow(isFirst: indexPath.row == 0, isLast: indexPath.row == tableContent.count - 1)
cell.initWithPositionAndShadow(
isFirst: indexPath.row == 0,
isLast: indexPath.row == GeneralParameterRow.allCases.count - 1
)
cell.titleLabel.text = row.title
return cell
}
}

private func mykSuiteCell(_ tableView: UITableView, forRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(type: ParameterTableViewCell.self, for: indexPath)
cell.initWithPositionAndShadow(
isFirst: indexPath.row == 0,
isLast: indexPath.row == MykSuiteParameterRow.allCases.count - 1
)

let row = MykSuiteParameterRow.allCases[indexPath.row]
switch row {
case .email:
cell.titleLabel.text = row.title
cell.titleLabel.font = TextStyle.body1.font
cell.selectionStyle = .none
case .mySubscription:
cell.titleLabel.text = row.title
}
return cell
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = tableContent[indexPath.row]
tableView.deselectRow(at: indexPath, animated: true)

guard mykSuiteEnabled else {
didSelectGeneralRowAt(indexPath: indexPath)
return
}

switch indexPath.section {
case ParameterSection.mykSuite.rawValue:
didSelectMykSuiteRowAt(indexPath: indexPath)
case ParameterSection.general.rawValue:
didSelectGeneralRowAt(indexPath: indexPath)
default:
return
}
}

private func didSelectMykSuiteRowAt(indexPath: IndexPath) {
let row = MykSuiteParameterRow.allCases[indexPath.row]
guard row == MykSuiteParameterRow.mySubscription else { return }
guard let currentAccount = accountManager.currentAccount else { return }
let dashboardViewController = MyKSuiteDashboardViewBridgeController.instantiate(
apiFetcher: driveFileManager.apiFetcher,
currentAccount: currentAccount
)
navigationController?.present(dashboardViewController, animated: true)
}

private func didSelectGeneralRowAt(indexPath: IndexPath) {
let row = GeneralParameterRow.allCases[indexPath.row]
switch row {
case .storage:
navigationController?.pushViewController(StorageTableViewController(style: .grouped), animated: true)
Expand All @@ -175,6 +329,22 @@ class ParameterTableViewController: BaseGroupedTableViewController {
navigationController?.present(deleteAccountViewController, animated: true)
}
}

private func checkMykSuiteEnabledAndRefresh() {
Task { @MainActor in
@InjectService var mykSuiteStore: MyKSuiteStore
let packIsMykSuite: Bool
if await mykSuiteStore.getMyKSuite(id: accountManager.currentUserId) != nil,
packId == .myKSuite || packId == .myKSuitePlus {
packIsMykSuite = true
} else {
packIsMykSuite = false
}

self.mykSuiteEnabled = packIsMykSuite
self.tableView.reloadData()
}
}
}

extension ParameterTableViewController: DeleteAccountDelegate {
Expand Down
44 changes: 44 additions & 0 deletions kDrive/UI/Controller/SwiftUI/AsyncImageView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Infomaniak kDrive - iOS App
Copyright (C) 2025 Infomaniak Network SA

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import Foundation
import InfomaniakCore
import SwiftUI

struct AsyncImageView: View {
@State private var loadedImage: UIImage?
let currentAccount: Account

var body: some View {
VStack {
if let image = loadedImage {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
} else {
Circle()
.fill(KDriveAsset.loaderDefaultColor.swiftUIColor)
.frame(width: 40, height: 40)
}
}
.task {
loadedImage = await currentAccount.user.getAvatar()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Infomaniak kDrive - iOS App
Copyright (C) 2025 Infomaniak Network SA

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import FloatingPanel
import InfomaniakCore
import InfomaniakCoreUIKit
import InfomaniakDI
import kDriveCore
import kDriveResources
import MyKSuite
import SwiftUI
import UIKit

enum MyKSuiteDashboardViewBridgeController {
static func instantiate(apiFetcher: DriveApiFetcher, currentAccount: Account) -> UIViewController {
let swiftUIView = MyKSuiteDashboardView(apiFetcher: apiFetcher, userId: currentAccount.userId) {
AsyncImageView(currentAccount: currentAccount)
}

return UIHostingController(rootView: swiftUIView)
}
}
Loading

0 comments on commit 5fd647b

Please sign in to comment.