Skip to content
Permalink
Browse files

[Dialogs] Theming action buttons in DialogThemer (#5416)

Adding semantic emphasis to Dialog actions which allows conditional theming of buttons. The MDCDailogScheme now has a button scheme which can be used to theme buttons according to their action's assigned emphasis.
Issue: b/117608629
Design Review document (go/gmdc-ios-dialogs-design-doc).
  • Loading branch information
galiak11 authored and jverkoey committed Oct 22, 2018
1 parent c7d4f27 commit 325772ba5e40ce8c27cadab5f0da727dea3d05a9
Showing with 707 additions and 128 deletions.
  1. +3 −0 MaterialComponents.podspec
  2. +22 −0 components/Dialogs/BUILD
  3. +91 −3 components/Dialogs/examples/DialogsAlertCustomizationViewController.swift
  4. +19 −2 components/Dialogs/src/ColorThemer/MDCAlertColorThemer.m
  5. +21 −1 components/Dialogs/src/DialogThemer/MDCAlertControllerThemer.m
  6. +7 −0 components/Dialogs/src/DialogThemer/MDCAlertScheme.h
  7. +1 −0 components/Dialogs/src/DialogThemer/MDCAlertScheme.m
  8. +33 −0 components/Dialogs/src/MDCAlertController+ButtonForAction.h
  9. +19 −0 components/Dialogs/src/MDCAlertController+ButtonForAction.m
  10. +55 −24 components/Dialogs/src/MDCAlertController.h
  11. +52 −23 components/Dialogs/src/MDCAlertController.m
  12. +1 −0 components/Dialogs/src/MDCAlertControllerView.h
  13. +11 −1 components/Dialogs/src/TypographyThemer/MDCAlertTypographyThemer.m
  14. +70 −0 components/Dialogs/src/private/MDCAlertActionManager.h
  15. +98 −0 components/Dialogs/src/private/MDCAlertActionManager.m
  16. +4 −4 components/Dialogs/src/private/MDCAlertControllerView+Private.h
  17. +40 −51 components/Dialogs/src/private/MDCAlertControllerView+Private.m
  18. +141 −0 components/Dialogs/tests/unit/MDCAlertActionManagerTests.m
  19. +4 −7 components/Dialogs/tests/unit/MDCAlertControllerAlertThemerTests.swift
  20. +2 −2 components/Dialogs/tests/unit/MDCAlertControllerColorThemerTests.m
  21. +12 −9 components/Dialogs/tests/unit/MDCAlertControllerTests.m
  22. +1 −1 components/Dialogs/tests/unit/MDCAlertControllerTypographyThemerTests.m
@@ -555,6 +555,7 @@ Pod::Spec.new do |mdc|

extension.dependency "MaterialComponents/#{extension.base_name.split('+')[0]}"
extension.dependency "MaterialComponents/Themes"
extension.dependency "MaterialComponents/Buttons+ColorThemer"
end

mdc.subspec "Dialogs+TypographyThemer" do |extension|
@@ -564,6 +565,7 @@ Pod::Spec.new do |mdc|

extension.dependency "MaterialComponents/#{extension.base_name.split('+')[0]}"
extension.dependency "MaterialComponents/schemes/Typography"
extension.dependency "MaterialComponents/Buttons+TypographyThemer"
end

mdc.subspec "Dialogs+DialogThemer" do |extension|
@@ -574,6 +576,7 @@ Pod::Spec.new do |mdc|
extension.dependency "MaterialComponents/#{extension.base_name.split('+')[0]}"
extension.dependency "MaterialComponents/Dialogs+ColorThemer"
extension.dependency "MaterialComponents/Dialogs+TypographyThemer"
extension.dependency "MaterialComponents/Buttons+ButtonThemer"
end

# FeatureHighlight
@@ -52,6 +52,25 @@ objc_bundle(
bundle_imports = [":BundleFiles"],
)

mdc_objc_library(
name = "DialogThemer",
srcs = native.glob(["src/DialogThemer/*.m"]),
hdrs = native.glob(["src/DialogThemer/*.h"]),
includes = ["src/DialogThemer"],
sdk_frameworks = [
"UIKit",
],
deps = [
":Dialogs",
"//components/Themes",
"//components/Buttons:ButtonThemer",
"//components/Buttons:ColorThemer",
":ColorThemer",
":TypographyThemer",
],
visibility = ["//visibility:public"],
)

mdc_objc_library(
name = "ColorThemer",
srcs = native.glob(["src/ColorThemer/*.m"]),
@@ -63,6 +82,7 @@ mdc_objc_library(
deps = [
":Dialogs",
"//components/Themes",
"//components/Buttons:ColorThemer",
],
visibility = ["//visibility:public"],
)
@@ -78,6 +98,7 @@ mdc_objc_library(
deps = [
":Dialogs",
"//components/schemes/Typography",
"//components/Buttons:TypographyThemer",
],
visibility = ["//visibility:public"],
)
@@ -102,6 +123,7 @@ mdc_objc_library(
],
deps = [
":Dialogs",
":DialogThemer",
":ColorThemer",
":TypographyThemer",
":private",
@@ -70,6 +70,10 @@ class DialogsAlertCustomizationViewController: MDCCollectionViewController {

var menu: [String] = []

var handler: MDCActionHandler = { action in
print(action.title ?? "Some Action")
}

override func viewDidLoad() {
super.viewDidLoad()

@@ -82,6 +86,11 @@ class DialogsAlertCustomizationViewController: MDCCollectionViewController {
"Right Aligned Title with a Large Icon",
"Tinted Title Icon, No Title",
"Darker Scrim",
"Emphasis-based Button Theming",
"Text Button Theming (will be deprecated)",
"Text Button Theming (the right way)",
"Custom Button Theming",
"Unthemed Alert",
])
}

@@ -109,6 +118,16 @@ class DialogsAlertCustomizationViewController: MDCCollectionViewController {
return performTintedTitleIconNoTitle()
case 5:
return performScrimColor()
case 6:
return performEmphasisButtonTheming()
case 7:
return performDeprecatedTextButtonTheming() // b/117717380: Will be deprecated
case 8:
return performTextButtonThemingTheRightWay()
case 9:
return performCustomButtonTheming()
case 10:
return performUnthemed()
default:
print("No row is selected")
return nil
@@ -157,7 +176,7 @@ class DialogsAlertCustomizationViewController: MDCCollectionViewController {
alert.titleIcon = sampleIcon()
MDCAlertControllerThemer.applyScheme(alertScheme, to: alert)

// theming override: set the titleIconTintColor after the color scheme has been applied
// Theming override: set the titleIconTintColor after the color scheme has been applied
alert.titleIconTintColor = .red

return alert
@@ -170,13 +189,82 @@ class DialogsAlertCustomizationViewController: MDCCollectionViewController {
return alert
}

func performEmphasisButtonTheming() -> MDCAlertController {
let alert = MDCAlertController(title: "Button Theming", message: "High, Medium & Low Emphasis")
alert.addAction(MDCAlertAction(title:"High", emphasis: .high, handler: handler))
alert.addAction(MDCAlertAction(title:"Medium", emphasis: .medium, handler: handler))
alert.addAction(MDCAlertAction(title:"Low", emphasis: .low, handler: handler))
MDCAlertControllerThemer.applyScheme(alertScheme, to: alert)
return alert
}

func performDeprecatedTextButtonTheming() -> MDCAlertController {
let alert = MDCAlertController(title: "Button Theming",
message: "This method of button theming will be deprecated")
// When not specified, the action is low emphasis by default
alert.addAction(MDCAlertAction(title:"Text", handler: handler))
alert.addAction(MDCAlertAction(title:"Text", handler: handler))
alert.addAction(MDCAlertAction(title:"Text", handler: handler))
MDCAlertControllerThemer.applyScheme(alertScheme, to: alert)
alert.buttonTitleColor = .orange // b/117717380: will be deprecated
return alert
}

// The right way to select the type of buttons is by setting empahsis for actions
func performTextButtonThemingTheRightWay() -> MDCAlertController {
let alert = MDCAlertController(title: "Button Theming",
message: "Use low emphasis to present buttons as text")
// Use .low emphasis to style buttons as text buttons.
alert.addAction(MDCAlertAction(title:"Text", emphasis: .low, handler: handler))
alert.addAction(MDCAlertAction(title:"Text", emphasis: .low, handler: handler))
alert.addAction(MDCAlertAction(title:"Text", emphasis: .low, handler: handler))
MDCAlertControllerThemer.applyScheme(alertScheme, to: alert)
return alert
}

func performCustomButtonTheming() -> MDCAlertController {
let alert = MDCAlertController(title: "Custom Button Theming",
message: "Custom styling of High, Medium & Low Emphasis")
alert.titleIcon = sampleIcon()

// Use .low emphasis for styling buttons as text buttons
alert.addAction(MDCAlertAction(title:"High", emphasis: .high, handler: handler))
alert.addAction(MDCAlertAction(title:"Medium", emphasis: .medium, handler: handler))
alert.addAction(MDCAlertAction(title:"Low", emphasis: .low, handler: handler))

let scheme = MDCAlertScheme()
scheme.typographyScheme = self.typographyScheme

// Create a color theme with a different primary color
let colorScheme = MDCSemanticColorScheme()
colorScheme.primaryColor = .blue

// Assign the new color theme to both the button and the alert schemes.
let buttonScheme = MDCButtonScheme()
buttonScheme.colorScheme = colorScheme
scheme.colorScheme = colorScheme
scheme.buttonScheme = buttonScheme

MDCAlertControllerThemer.applyScheme(scheme, to: alert)
return alert
}

func performUnthemed() -> MDCAlertController {
let alert = MDCAlertController(title: "Unthemed Alert",
message: "Lorem ipsum dolor sit amet, consectetur adipiscing...")
alert.addAction(MDCAlertAction(title:"High", emphasis: .high, handler: handler))
alert.addAction(MDCAlertAction(title:"Medium", emphasis: .medium, handler: handler))
alert.addAction(MDCAlertAction(title:"Low", emphasis: .low, handler: handler))
return alert
}

private func createMDCAlertController(title: String?) -> MDCAlertController {
let alertController = MDCAlertController(title: title, message: """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
""")
alertController.addAction(MDCAlertAction(title:"OK") { _ in print("OK") })
alertController.addAction(MDCAlertAction(title:"Cancel") { _ in print("Cancel") })
alertController.addAction(MDCAlertAction(title:"OK", handler: handler))
alertController.addAction(MDCAlertAction(title:"Cancel", handler: handler))
return alertController
}

@@ -14,18 +14,35 @@

#import "MDCAlertColorThemer.h"

#import "../../../Buttons/src/ColorThemer/MaterialButtons+ColorThemer.h"
#import "../MDCAlertController+ButtonForAction.h"
#import "MaterialButtons.h"
#import "MaterialDialogs.h"

@implementation MDCAlertColorThemer

+ (void)applySemanticColorScheme:(nonnull id<MDCColorScheming>)colorScheme
toAlertController:(nonnull MDCAlertController *)alertController {
alertController.titleColor = [colorScheme.onSurfaceColor colorWithAlphaComponent:(CGFloat)0.87];
alertController.messageColor = [colorScheme.onSurfaceColor colorWithAlphaComponent:(CGFloat)0.60];
alertController.buttonTitleColor = colorScheme.primaryColor;
alertController.titleIconTintColor = colorScheme.primaryColor;
alertController.scrimColor = [colorScheme.onSurfaceColor colorWithAlphaComponent:(CGFloat)0.32];

// Apply theming to buttons based on the action emphasis
for (MDCAlertAction *action in alertController.actions) {
MDCButton *button = [alertController buttonForAction:action];
switch (action.emphasis) {
case MDCActionEmphasisHigh:
[MDCContainedButtonColorThemer applySemanticColorScheme:colorScheme toButton:button];
break;
case MDCActionEmphasisMedium:
[MDCOutlinedButtonColorThemer applySemanticColorScheme:colorScheme toButton:button];
break;
case MDCActionEmphasisLow: // fallthrough
default:
[MDCTextButtonColorThemer applySemanticColorScheme:colorScheme toButton:button];
break;
}
}
}

+ (void)applyColorScheme:(id<MDCColorScheme>)colorScheme {
@@ -13,8 +13,11 @@
// limitations under the License.

#import "MDCAlertControllerThemer.h"

#import "../MDCAlertController+ButtonForAction.h"
#import "MDCAlertColorThemer.h"
#import "MDCAlertTypographyThemer.h"
#import "MaterialButtons+ButtonThemer.h"

@implementation MDCAlertControllerThemer

@@ -28,6 +31,23 @@ + (void)applyScheme:(nonnull id<MDCAlertScheming>)alertScheme

alertController.cornerRadius = alertScheme.cornerRadius;
alertController.elevation = alertScheme.elevation;
}

// Apply theming to buttons based on the action emphasis
for (MDCAlertAction *action in alertController.actions) {
MDCButton *button = [alertController buttonForAction:action];
// todo: b/117265609: Incorporate dynamic type support in semantic themers
switch (action.emphasis) {
case MDCActionEmphasisHigh:
[MDCContainedButtonThemer applyScheme:alertScheme.buttonScheme toButton:button];
break;
case MDCActionEmphasisMedium:
[MDCOutlinedButtonThemer applyScheme:alertScheme.buttonScheme toButton:button];
break;
case MDCActionEmphasisLow: // fallthrough
default:
[MDCTextButtonThemer applyScheme:alertScheme.buttonScheme toButton:button];
break;
}
}
}
@end
@@ -14,6 +14,7 @@

#import <Foundation/Foundation.h>

#import "MaterialButtons+ButtonThemer.h"
#import "MaterialColorScheme.h"
#import "MaterialShadowElevations.h"
#import "MaterialTypographyScheme.h"
@@ -27,6 +28,9 @@
/** The typography scheme to apply to Dialog. */
@property(nonnull, readonly, nonatomic) id<MDCTypographyScheming> typographyScheme;

/** The button scheme to apply to Dialog's actions. */
@property(nonnull, readonly, nonatomic) id<MDCButtonScheming> buttonScheme;

/** The corner radius to apply to Dialog. */
@property(readonly, nonatomic) CGFloat cornerRadius;

@@ -45,6 +49,9 @@
/** The typography scheme to apply to Dialog. */
@property(nonnull, readwrite, nonatomic) id<MDCTypographyScheming> typographyScheme;

/** The button scheme to apply to Dialog's actions. */
@property(nonnull, readwrite, nonatomic) id<MDCButtonScheming> buttonScheme;

/** The corner radius to apply to Dialog. */
@property(readwrite, nonatomic) CGFloat cornerRadius;

@@ -23,6 +23,7 @@ - (instancetype)init {
if (self) {
_colorScheme = [[MDCSemanticColorScheme alloc] init];
_typographyScheme = [[MDCTypographyScheme alloc] init];
_buttonScheme = [[MDCButtonScheme alloc] init];
_cornerRadius = kCornerRadius;
_elevation = MDCShadowElevationDialog;
}
@@ -0,0 +1,33 @@
// Copyright 2018-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import "MaterialDialogs.h"

@interface MDCAlertController (ButtonForAction)

/**
Returns an MDCButton associated with the given action. This method might create the button if
it no associated button exist for the action yet. Buttons returned by thismethod may not (yet)
be attached to the view hierarchy at the time the method is called.
This method is commonly used by themers to style the button associated with the action.
@param action The action with which the button is associated. Must be an existing action that
had been previously added through addAction: to the alert.
@return The button associated with the action, or nil if the action doesn't exist (the action
must first be added to the alert).
*/
- (nullable MDCButton *)buttonForAction:(nonnull MDCAlertAction *)action;

@end
@@ -0,0 +1,19 @@
// Copyright 2018-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import "MDCAlertController+ButtonForAction.h"

@implementation MDCAlertController (buttonForAction)

@end

0 comments on commit 325772b

Please sign in to comment.
You can’t perform that action at this time.