Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 2.1.0 #771

Closed
wants to merge 64 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
e951f45
Parse GVA JSON data to platform-specific data structures
rasmustautsglia Jul 6, 2023
242f7e3
GVA Response Text UI
rasmustautsglia Jul 11, 2023
9fd216d
Handle HTML text in GVA messages
rasmustautsglia Jul 12, 2023
9bb0a03
Added actions for GVA buttons
Jul 14, 2023
08c529f
Unsupported broadcast events alert action
Jul 17, 2023
54fd772
Add GVA Persistent button UI
rasmustautsglia Jul 18, 2023
b17acb2
Adjust layout to match snapshots
igorkravchenko Jul 17, 2023
f94359b
Apply Unified Customization to Persistent Button
rasmustautsglia Jul 19, 2023
09e68de
Add QuickReplyButton UI
Jul 19, 2023
580f37d
Add Quick Reply button to RemoteConfiguration
Jul 24, 2023
9ba22bd
Introduce snapshot tests for dynamic type font
igorkravchenko Jul 24, 2023
dba7ae4
Fix custom cards handling
yurii-glia Jul 25, 2023
a9b7214
Add snapshot test for bubble window
igorkravchenko Jul 26, 2023
e642b03
Add missing acc. identifiers and custom segmented control
igorkravchenko Jul 10, 2023
1fc066c
Add GVA GalleryCard handling
Aug 2, 2023
70a2e13
Cover GVA persistent button with snapshot tests
rasmustautsglia Aug 3, 2023
ded5340
Add Snapshot tests for gva response text
rasmustautsglia Aug 9, 2023
67f3e42
Fix SwiftLint warnings
gersonnoboa Aug 10, 2023
d4c5894
Cover GVA Quick Replies with snapshot tests
rasmustautsglia Aug 10, 2023
c2cad0c
Increment project version to 2.0.6
Aug 10, 2023
6ceaf26
Change GVA Related String
rasmustautsglia Aug 11, 2023
4cce631
Cover GVA Gallery with Snapshot tests
rasmustautsglia Aug 11, 2023
ee8498d
Apply Unified Customization to Gallery Card
rasmustautsglia Aug 10, 2023
10f821d
Fix dark mode issues in GVA
rasmustautsglia Aug 11, 2023
8adcd17
Update background usage in SurveyStyle.InputQuestion schema
yurii-glia Aug 14, 2023
e03cc10
Remove outdated visitor info keys
igorkravchenko Aug 16, 2023
197df4e
System Message not shown in chat
rasmustautsglia Aug 17, 2023
5cd0ce2
Discard duplicate messages delivered by sockets
igorkravchenko Aug 17, 2023
5770796
Reworked styles for chat message views
Aug 18, 2023
ba15c1a
Fix missing operator image after reusing GvaGalleryListView
Aug 21, 2023
4ba27c6
Gallery Card not rendered correctly when image url is invalid
rasmustautsglia Aug 22, 2023
921a7f6
Add handling `glia://widgets/settings` deeplink
Aug 21, 2023
59bca12
Increment project version to 2.0.7
Aug 22, 2023
33db5ad
Postback button actions caching
Aug 23, 2023
2cc383e
Capitalize accessibility identifier for back button
gersonnoboa Aug 25, 2023
364bd85
Fix ChatView TableView crash when scrolling to bottom
rasmustautsglia Aug 25, 2023
fa93bec
Show Quick Replies for visitor when the QR is the latest in history
rasmustautsglia Aug 25, 2023
5f8a4b8
Fix layout inconsistencies in ChatView
rasmustautsglia Aug 28, 2023
1001f61
Send postback button actions from Secure conversations chat transcript
Aug 28, 2023
a1d3994
Show Quick Replies for SecConv for visitor when the QR is the latest …
Aug 29, 2023
36b8165
Fix operator image displaying for GVA messages
Aug 30, 2023
38c2af9
Remove space between user message and operator header.
rasmustautsglia Aug 30, 2023
e440933
Conform message center input to dynamic type font
rasmustautsglia Aug 30, 2023
3910fbb
Introduce minimize() public interface
Sep 1, 2023
e2d2cce
Improve URLs handling in text messages
Sep 1, 2023
60f1dc4
Fix failed unit test visitorAppDefaultLocale error
yurii-glia Sep 4, 2023
1d26128
Update Core SDK to 1.1.0
yurii-glia Sep 4, 2023
eb5eda3
Remove unnecessary accessibility hint in Call Visualizer
gersonnoboa Sep 6, 2023
7b057c1
Bump Core SDK version to 1.1.1
igorkravchenko Sep 6, 2023
2c9728b
Enable visitor message syncronization for regular chat engagement
igorkravchenko Sep 4, 2023
aba96e9
Remove Call Visualizer Constraint Warnings
rasmustautsglia Sep 4, 2023
7e35876
Fix status for GVA delivered messages
Sep 6, 2023
c87e36f
Make Buttons ADA compliant
rasmustautsglia Sep 8, 2023
e60ce62
Fix for SingleChoiceResponse message duplications
Sep 8, 2023
76ef4db
Make chat call upgrade to audio view ADA compliant
rasmustautsglia Sep 11, 2023
ec2f76a
Enable 'powered by Glia' in default Theme
igorkravchenko Sep 11, 2023
970a5cc
Make screen sharing view ADA compliant
rasmustautsglia Sep 11, 2023
79affff
Make CallView bottomLabel ADA Compliant
rasmustautsglia Sep 12, 2023
f7eae32
Cover changes introduced by Web synchronization with unit tests
igorkravchenko Sep 12, 2023
1874acb
Update CoreSDK to 1.1.2
yurii-glia Sep 13, 2023
58900b2
Make interface for enqueueing with multiple queue
yurii-glia Sep 5, 2023
5d935ff
Fix visitor-on-hold and unread message count in bubble-view
igorkravchenko Sep 13, 2023
f3f771f
Introduce list queue interface
yurii-glia Sep 6, 2023
18c41d9
Introduce list queue interface
yurii-glia Sep 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add missing acc. identifiers and custom segmented control
Add more accessibility identifiers to visitor info UI and replace regular UISegmentedControl with custom implementation to overcome Voice Over (screen reader) issue, where views order is broken, if such control is used in section header.

MOB-1100
  • Loading branch information
igorkravchenko authored and yurii-glia committed Sep 14, 2023
commit e642b038614e7550fc23640cd4e9c21919e06770
4 changes: 4 additions & 0 deletions GliaWidgets.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@
AF6AB34B2989517100003645 /* FileUploader.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF6AB34A2989517100003645 /* FileUploader.Failing.swift */; };
AF6AB34D298A9F2500003645 /* SecureConversations.FileUploadListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF6AB34C298A9F2500003645 /* SecureConversations.FileUploadListViewModel.swift */; };
AF6AB34F298AA02400003645 /* SecureConversations.FileUploadListViewModel.Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF6AB34E298AA02400003645 /* SecureConversations.FileUploadListViewModel.Environment.swift */; };
AF75601C2A78146600871E36 /* CustomSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF75601B2A78146500871E36 /* CustomSegmentedControl.swift */; };
AF755FDA2A71583900871E36 /* BubbleWindowLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF755FD92A71583900871E36 /* BubbleWindowLayoutTests.swift */; };
AF9DB22E2890571A00A0C442 /* RootCoordinator.Environment.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9DB22D2890571A00A0C442 /* RootCoordinator.Environment.Failing.swift */; };
AF9DB23128905A1D00A0C442 /* ViewFactory.Environment.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9DB23028905A1D00A0C442 /* ViewFactory.Environment.Failing.swift */; };
Expand Down Expand Up @@ -1208,6 +1209,7 @@
AF6AB34A2989517100003645 /* FileUploader.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploader.Failing.swift; sourceTree = "<group>"; };
AF6AB34C298A9F2500003645 /* SecureConversations.FileUploadListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.FileUploadListViewModel.swift; sourceTree = "<group>"; };
AF6AB34E298AA02400003645 /* SecureConversations.FileUploadListViewModel.Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.FileUploadListViewModel.Environment.swift; sourceTree = "<group>"; };
AF75601B2A78146500871E36 /* CustomSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSegmentedControl.swift; sourceTree = "<group>"; };
AF755FD92A71583900871E36 /* BubbleWindowLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleWindowLayoutTests.swift; sourceTree = "<group>"; };
AF9DB22D2890571A00A0C442 /* RootCoordinator.Environment.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootCoordinator.Environment.Failing.swift; sourceTree = "<group>"; };
AF9DB23028905A1D00A0C442 /* ViewFactory.Environment.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFactory.Environment.Failing.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3262,6 +3264,7 @@
AFD3C52A2A472B1C00BC37A9 /* VisitorInfo */ = {
isa = PBXGroup;
children = (
AF75601B2A78146500871E36 /* CustomSegmentedControl.swift */,
AF3BD1DC2A41D09100A7713E /* VisitorInfoViewController.swift */,
AF35DFF02A507CA70038BCA7 /* VisitorInfoViewController.Cells.swift */,
AF35DFEE2A507B4C0038BCA7 /* VisitorInfoViewController.HeaderView.swift */,
Expand Down Expand Up @@ -4571,6 +4574,7 @@
7552DFA52A683A2C0093519B /* Makeable.swift in Sources */,
1A205D7F25655CEC003AA3CD /* ViewController.swift in Sources */,
AFD3C52C2A472B7500BC37A9 /* VisitorInfoModel.swift in Sources */,
AF75601C2A78146600871E36 /* CustomSegmentedControl.swift in Sources */,
1A4AD3D6256FE7F800468BFB /* UIColor+Extensions.swift in Sources */,
AF11F30728BE6F0C002ACEB4 /* UIAlertController+Extensions.swift in Sources */,
7552DFA82A683A2C0093519B /* UIStackView.Extensions.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions TestingApp/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tYL-hF-sF1">
<rect key="frame" x="152" y="421.5" width="110" height="30"/>
<accessibility key="accessibilityConfiguration" identifier="main_visitorInfo_button"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Show vistor info"/>
Expand Down
171 changes: 171 additions & 0 deletions TestingApp/VisitorInfo/CustomSegmentedControl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import UIKit

protocol CustomSegmentedControlDelegate: AnyObject {
func segmentedControl(_ segmentedControl: CustomSegmentedControl, didSelectSegmentAt index: Int)
}

class CustomSegmentedControl: UIControl {
weak var delegate: CustomSegmentedControlDelegate?

private var buttons = [UIButton]()
private (set) var selectedSegmentIndex = 0

var segments: [String] = [] {
didSet {
setupSegments()
}
}

var textColor: UIColor = .gray {
didSet {
updateButtonAppearance()
}
}

var selectedTextColor: UIColor = .label {
didSet {
updateButtonAppearance()
}
}

var segmentSpacing: CGFloat = 8 {
didSet {
stackView.spacing = segmentSpacing
invalidateIntrinsicContentSize()
}
}

var cornerRadius: CGFloat = 8 {
didSet {
layer.cornerRadius = cornerRadius
}
}

var borderWidth: CGFloat = 1 {
didSet {
layer.borderWidth = borderWidth
}
}

var borderColor: UIColor = .gray {
didSet {
layer.borderColor = borderColor.cgColor
}
}

private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.spacing = segmentSpacing
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 10, bottom: 0, right: 10)
return stackView
}()

// MARK: - Initialization

override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}

// MARK: - View setup

private func setupView() {
layer.cornerRadius = cornerRadius
layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor
backgroundColor = .clear

addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])

setupSegments()
}

private func setupSegments() {
// Remove previous buttons
buttons.forEach { $0.removeFromSuperview() }
buttons.removeAll(keepingCapacity: true)

// Create and add buttons for each segment
for title in segments {
let button = UIButton(type: .custom)
button.setTitle(title, for: .normal)
button.addTarget(self, action: #selector(segmentTapped(_:)), for: .touchUpInside)
buttons.append(button)
stackView.addArrangedSubview(button)
}

// Update UI appearance
updateButtonAppearance()
}

private func updateButtonAppearance() {
for (index, button) in zip(0..., buttons) {
button.tintColor = .clear
button.setTitleColor(
index == selectedSegmentIndex ? selectedTextColor : textColor,
for: .normal
)
let fontSize = UIFont.systemFontSize - 2

button.titleLabel?.font = index == selectedSegmentIndex ? .boldSystemFont(ofSize: fontSize) :
.systemFont(ofSize: fontSize)
}
}

// MARK: - Segment Handling

@objc private func segmentTapped(_ sender: UIButton) {
guard let index = buttons.firstIndex(of: sender) else { return }
setSelectedSegment(index, sendActions: true)
}

private func setSelectedSegment(_ index: Int, sendActions: Bool) {
if selectedSegmentIndex != index {
selectedSegmentIndex = index
updateButtonAppearance()
if sendActions {
self.sendActions(for: .valueChanged)
delegate?.segmentedControl(self, didSelectSegmentAt: index)
}
}
}

// MARK: - Public API

func setSelectedSegmentIndex(_ index: Int, sendActions: Bool) {
guard segments.indices.contains(index) else { return }
setSelectedSegment(index, sendActions: sendActions)
}

func setTitle(_ title: String, forSegmentAt index: Int) {
guard segments.indices.contains(index) else { return }
segments[index] = title
buttons[index].setTitle(title, for: .normal)
invalidateIntrinsicContentSize()
}

func titleForSegment(at index: Int) -> String? {
guard segments.indices.contains(index) else { return nil }
return segments[index]
}

func setAccessibilityIdentifier(_ identifier: String, at index: Int) {
guard segments.indices.contains(index) else { return }
stackView.arrangedSubviews[index].accessibilityIdentifier = identifier
}
}
14 changes: 12 additions & 2 deletions TestingApp/VisitorInfo/VisitorInfoModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class VisitorInfoModel {
typealias Segment = HeaderView.Props.Segment
typealias Delegate = (DelegateEvent) -> Void
typealias UpdateButton = Props.UpdateButton
typealias CancelButton = Props.CancelButton

// Stores visitor fields to be sent for update.
var visitorInfoUpdate = VisitorInfoUpdate() {
Expand Down Expand Up @@ -351,11 +352,20 @@ final class VisitorInfoModel {
let updateButton: UpdateButton
switch self.updateInfoRequestState {
case .none, .result:
updateButton = .normal(updateTap, isEnabled: hasChanges)
updateButton = .normal(
updateTap,
isEnabled: hasChanges,
accIdentifier: "visitor_info_save_button"
)
case .inFlight:
updateButton = .loading
}

let cancelButton = CancelButton(
tap: cancelTap,
accIdentifier: "visitor_info_cancel_button"
)

let props = Props(
title: "Visitor Info",
sections: [
Expand All @@ -365,7 +375,7 @@ final class VisitorInfoModel {
],
pulledToRefresh: pulledToRefresh,
showsRefreshing: self.fetchInfoRequestState == .inFlight,
cancelTap: cancelTap,
cancelButton: cancelButton,
updateButton: updateButton
)

Expand Down
48 changes: 25 additions & 23 deletions TestingApp/VisitorInfo/VisitorInfoViewController.HeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ final class HeaderView: UITableViewHeaderFooterView {
static var height: CGFloat { UITableView.automaticDimension }

private lazy var titleLabel = UILabel()
private lazy var segmentedControl = UISegmentedControl()
private lazy var customSegmentedControl = CustomSegmentedControl(frame: .zero).makeView()
private lazy var actionButton = UIButton(type: .system)

var props = Props(title: "", segments: [], selectedSegment: nil, actionButton: nil, segmentAccessibilityIdentifierPrefix: "") {
var props = Props(
title: "",
segments: [],
selectedSegment: nil,
actionButton: nil,
segmentAccessibilityIdentifierPrefix: ""
) {
didSet {
guard props != oldValue else { return }
renderProps()
Expand All @@ -35,8 +41,8 @@ final class HeaderView: UITableViewHeaderFooterView {
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)

stackView.addArrangedSubview(segmentedControl)
segmentedControl.addTarget(
stackView.addArrangedSubview(customSegmentedControl)
customSegmentedControl.addTarget(
self,
action: #selector(handleSegmentedControlSelect),
for: .valueChanged
Expand All @@ -58,6 +64,7 @@ final class HeaderView: UITableViewHeaderFooterView {
titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -margins),
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -margins),
customSegmentedControl.heightAnchor.constraint(equalToConstant: 40)
])
}

Expand All @@ -78,35 +85,31 @@ final class HeaderView: UITableViewHeaderFooterView {

// Render list of segments if needed.
if renderedSegments.segments != oldValue.segments {
segmentedControl.removeAllSegments()
customSegmentedControl.segments = renderedSegments.segments.map(\.title)

for (segment, idx) in zip(renderedSegments.segments, 0...) {
segmentedControl.insertSegment(withTitle: segment.title, at: idx, animated: false)
if renderedSegments.selectedSegment == segment {
selectedIdx = idx
}

customSegmentedControl.setAccessibilityIdentifier(
"\(props.segmentAccessibilityIdentifierPrefix)\(idx)",
at: idx
)
}

// Render segment selection if needed, to preserve uninterrupted animation.
// Render segment selection if needed.
} else if renderedSegments.selectedSegment != oldValue.selectedSegment {
for (segment, idx) in zip(renderedSegments.segments, 0...) {
if renderedSegments.selectedSegment == segment {
selectedIdx = idx
}
for (segment, idx) in zip(renderedSegments.segments, 0...) where renderedSegments.selectedSegment == segment {
selectedIdx = idx
}
}

if let selectedIdx, segmentedControl.selectedSegmentIndex != selectedIdx {
segmentedControl.selectedSegmentIndex = selectedIdx
// Segments of UISegmentedControl are private, so there's no direct way to access them
// to assign accessibility identifier, so we need to use workaround for that.
var idx = 0
for view in segmentedControl.subviews where "\(type(of: view))".lowercased().hasSuffix("segment") {
view.accessibilityIdentifier = "\(props.segmentAccessibilityIdentifierPrefix)\(idx)"
idx += 1
}
if let selectedIdx, customSegmentedControl.selectedSegmentIndex != selectedIdx {
customSegmentedControl.setSelectedSegmentIndex(selectedIdx, sendActions: false)
}

segmentedControl.isHidden = renderedSegments.segments.isEmpty
customSegmentedControl.isHidden = renderedSegments.segments.isEmpty
}
}

Expand All @@ -120,11 +123,10 @@ final class HeaderView: UITableViewHeaderFooterView {
} else {
props.actionButton?.tap()
}

}

@objc
private func handleSegmentedControlSelect(_ control: UISegmentedControl) {
private func handleSegmentedControlSelect(_ control: CustomSegmentedControl) {
props.segments[control.selectedSegmentIndex].select()
}
}
Expand Down
Loading