Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Unify Server & Connection settings into 1 row (#1255)
Extremely long horizon: multiple of these could be shown to configure the settings of a particular server. Copies the look and feel of the 'avatar' initials and image loading from the frontend. When an avatar isn't set or isn't loaded yet, this shows the initials view.
- Loading branch information
Showing
5 changed files
with
212 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import Shared | ||
import UIKit | ||
|
||
enum AccountInitialsImage { | ||
private static func initials(for string: String?) -> String { | ||
// swiftlint:disable:next line_length | ||
// matching https://github.com/home-assistant/frontend/blob/42bf350034b7a53f0c6ba76791ea9d2a65bf6d67/src/components/user/ha-user-badge.ts | ||
|
||
guard let string = string else { | ||
return "?" | ||
} | ||
|
||
return string | ||
.trimmingCharacters(in: .whitespacesAndNewlines) | ||
.split(separator: " ") | ||
.prefix(3) | ||
.compactMap { $0.first.map(String.init(_:)) } | ||
.joined() | ||
} | ||
|
||
static func image(for name: String?, size: CGSize) -> UIImage { | ||
let initials = self.initials(for: name) | ||
|
||
let rect = CGRect(origin: .zero, size: size) | ||
let image = UIGraphicsImageRenderer(size: size).image { context in | ||
Constants.tintColor.setFill() | ||
context.fill(rect) | ||
|
||
let fontSize = size.height / (initials.count >= 3 ? 3 : 2) | ||
|
||
let initials = NSMutableAttributedString( | ||
string: initials, | ||
attributes: [ | ||
.font: UIFont.systemFont(ofSize: fontSize, weight: .regular), | ||
.foregroundColor: UIColor.white | ||
] | ||
) | ||
let initialsSize = initials | ||
.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil) | ||
.size | ||
let initialsRect = rect.insetBy( | ||
dx: max(4, (size.width - initialsSize.width) / 2.0), | ||
dy: max(4, (size.height - initialsSize.height) / 2.0) | ||
) | ||
initials.draw( | ||
with: initialsRect, | ||
options: [.usesLineFragmentOrigin], | ||
context: nil | ||
) | ||
} | ||
image.accessibilityLabel = initials | ||
return image | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import Foundation | ||
import Eureka | ||
import Shared | ||
import PromiseKit | ||
|
||
class AccountCell: Cell<HomeAssistantAccountRowInfo>, CellType { | ||
private var accountRow: HomeAssistantAccountRow? { return row as? HomeAssistantAccountRow } | ||
|
||
override func setup() { | ||
super.setup() | ||
|
||
imageView?.layer.masksToBounds = true | ||
|
||
textLabel?.font = UIFont.preferredFont(forTextStyle: .body) | ||
detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body) | ||
|
||
selectionStyle = .default | ||
accessoryType = .disclosureIndicator | ||
} | ||
|
||
override func update() { | ||
super.update() | ||
|
||
let userName = accountRow?.value?.user?.Name | ||
let locationName = accountRow?.value?.locationName | ||
|
||
let height = min(64, UIFont.preferredFont(forTextStyle: .body).lineHeight * 2.0) | ||
let size = CGSize(width: height, height: height) | ||
|
||
if let imageView = imageView { | ||
if let image = accountRow?.cachedImage { | ||
UIView.transition( | ||
with: imageView, | ||
duration: imageView.image != nil ? 0.25 : 0, | ||
options: [.transitionCrossDissolve] | ||
) { | ||
// scaled down because the cell sizes to fit too much | ||
imageView.image = image.scaledToSize(size) | ||
} completion: { _ in | ||
|
||
} | ||
} else { | ||
imageView.image = AccountInitialsImage | ||
.image( | ||
for: userName, | ||
size: CGSize(width: height, height: height) | ||
) | ||
} | ||
|
||
imageView.layer.cornerRadius = ceil(height / 2.0) | ||
} | ||
|
||
textLabel?.text = locationName | ||
detailTextLabel?.text = userName | ||
|
||
if #available(iOS 13, *) { | ||
detailTextLabel?.textColor = .secondaryLabel | ||
} else { | ||
detailTextLabel?.textColor = .darkGray | ||
} | ||
} | ||
} | ||
|
||
struct HomeAssistantAccountRowInfo: Equatable { | ||
var user: AuthenticatedUser? | ||
var locationName: String? | ||
|
||
static func == (lhs: HomeAssistantAccountRowInfo, rhs: HomeAssistantAccountRowInfo) -> Bool { | ||
return lhs.user?.ID == rhs.user?.ID && | ||
lhs.locationName == rhs.locationName | ||
} | ||
} | ||
|
||
final class HomeAssistantAccountRow: Row<AccountCell>, RowType { | ||
var presentationMode: PresentationMode<UIViewController>? | ||
|
||
override func customDidSelect() { | ||
super.customDidSelect() | ||
if !isDisabled { | ||
if let presentationMode = presentationMode { | ||
if let controller = presentationMode.makeController() { | ||
presentationMode.present(controller, row: self, presentingController: cell.formViewController()!) | ||
} else { | ||
presentationMode.present(nil, row: self, presentingController: cell.formViewController()!) | ||
} | ||
} | ||
} | ||
} | ||
|
||
required init(tag: String?) { | ||
super.init(tag: tag) | ||
self.cellStyle = .subtitle | ||
} | ||
|
||
fileprivate var cachedImage: UIImage? | ||
|
||
override var value: Cell.Value? { | ||
didSet { | ||
if value != oldValue { | ||
fetchAvatar() | ||
} | ||
} | ||
} | ||
|
||
private func fetchAvatar() { | ||
guard let user = value?.user else { | ||
cachedImage = nil | ||
return | ||
} | ||
|
||
firstly { | ||
HomeAssistantAPI.authenticatedAPIPromise | ||
}.then { | ||
$0.GetStates() | ||
}.firstValue { | ||
$0.Attributes["user_id"] as? String == user.ID | ||
}.compactMap { | ||
$0.Attributes["entity_picture"] as? String | ||
}.compactMap { | ||
Current.settingsStore.connectionInfo?.activeURL.appendingPathComponent($0) | ||
}.then { | ||
URLSession.shared.dataTask(.promise, with: $0) | ||
}.compactMap { | ||
UIImage(data: $0.data) | ||
}.done { [self] image in | ||
Current.Log.verbose("got image \(image.size)") | ||
cachedImage = image | ||
updateCell() | ||
}.catch { error in | ||
Current.Log.error("failed to grab thumbnail: \(error)") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters