Commit 5d3c7435 authored by Gauthier Ambard's avatar Gauthier Ambard Committed by Commit Bot

[iOS] Update Encryption to UITableView

This CL updates the EncryptionCollectionVC to use a UITableVC instead
of the MDCCollectionVC.

Bug: 894791
Change-Id: I875bc29cb3a7b27242897e7301a0e7fe761d1fcd
Reviewed-on: https://chromium-review.googlesource.com/c/1350832
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarSergio Collazos <sczs@chromium.org>
Reviewed-by: default avatarChris Lu <thegreenfrog@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612116}
parent cd8b2fb8
...@@ -92,10 +92,10 @@ source_set("settings") { ...@@ -92,10 +92,10 @@ source_set("settings") {
"settings_utils.mm", "settings_utils.mm",
"sync_create_passphrase_collection_view_controller.h", "sync_create_passphrase_collection_view_controller.h",
"sync_create_passphrase_collection_view_controller.mm", "sync_create_passphrase_collection_view_controller.mm",
"sync_encryption_collection_view_controller.h",
"sync_encryption_collection_view_controller.mm",
"sync_encryption_passphrase_collection_view_controller.h", "sync_encryption_passphrase_collection_view_controller.h",
"sync_encryption_passphrase_collection_view_controller.mm", "sync_encryption_passphrase_collection_view_controller.mm",
"sync_encryption_table_view_controller.h",
"sync_encryption_table_view_controller.mm",
"sync_settings_collection_view_controller.h", "sync_settings_collection_view_controller.h",
"sync_settings_collection_view_controller.mm", "sync_settings_collection_view_controller.mm",
"table_cell_catalog_view_controller.h", "table_cell_catalog_view_controller.h",
...@@ -292,8 +292,8 @@ source_set("unit_tests") { ...@@ -292,8 +292,8 @@ source_set("unit_tests") {
"settings_root_collection_view_controller_unittest.mm", "settings_root_collection_view_controller_unittest.mm",
"settings_root_table_view_controller_unittest.mm", "settings_root_table_view_controller_unittest.mm",
"sync_create_passphrase_collection_view_controller_unittest.mm", "sync_create_passphrase_collection_view_controller_unittest.mm",
"sync_encryption_collection_view_controller_unittest.mm",
"sync_encryption_passphrase_collection_view_controller_unittest.mm", "sync_encryption_passphrase_collection_view_controller_unittest.mm",
"sync_encryption_table_view_controller_unittest.mm",
"sync_settings_collection_view_controller_unittest.mm", "sync_settings_collection_view_controller_unittest.mm",
"time_range_selector_collection_view_controller_unittest.mm", "time_range_selector_collection_view_controller_unittest.mm",
"translate_table_view_controller_unittest.mm", "translate_table_view_controller_unittest.mm",
......
...@@ -7,14 +7,13 @@ ...@@ -7,14 +7,13 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h" #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
#import "ios/third_party/material_components_ios/src/components/CollectionCells/src/MaterialCollectionCells.h"
// Item displaying possible options in the Sync Encryption screen. // Item displaying possible options in the Sync Encryption screen.
@interface EncryptionItem : CollectionViewItem @interface EncryptionItem : TableViewItem
// The accessory type for the represented cell. // The accessory type for the represented cell.
@property(nonatomic) MDCCollectionViewCellAccessoryType accessoryType; @property(nonatomic) UITableViewCellAccessoryType accessoryType;
// The text to display. // The text to display.
@property(nonatomic, copy) NSString* text; @property(nonatomic, copy) NSString* text;
...@@ -26,7 +25,7 @@ ...@@ -26,7 +25,7 @@
@end @end
// The cell associated to |EncryptionCell|. // The cell associated to |EncryptionCell|.
@interface EncryptionCell : MDCCollectionViewCell @interface EncryptionCell : UITableViewCell
// UILabel corresponding to |text| from the item. // UILabel corresponding to |text| from the item.
@property(nonatomic, readonly, strong) UILabel* textLabel; @property(nonatomic, readonly, strong) UILabel* textLabel;
......
...@@ -4,32 +4,14 @@ ...@@ -4,32 +4,14 @@
#import "ios/chrome/browser/ui/settings/cells/encryption_item.h" #import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h" #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
#include "ios/chrome/browser/ui/collection_view/cells/collection_view_cell_constants.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h" #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h" #import "ios/chrome/common/ui_util/constraints_ui_util.h"
#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
namespace {
// Padding used on the leading and trailing edges of the cell.
const CGFloat kHorizontalPadding = 16;
// Padding used on the top and bottom edges of the cell.
const CGFloat kVerticalPadding = 16;
} // namespace
@interface EncryptionCell ()
// Returns the default text color used for the given |enabled| state.
+ (UIColor*)defaultTextColorForEnabledState:(BOOL)enabled;
@end
@implementation EncryptionItem @implementation EncryptionItem
@synthesize accessoryType = _accessoryType; @synthesize accessoryType = _accessoryType;
...@@ -46,12 +28,14 @@ const CGFloat kVerticalPadding = 16; ...@@ -46,12 +28,14 @@ const CGFloat kVerticalPadding = 16;
return self; return self;
} }
- (void)configureCell:(EncryptionCell*)cell { - (void)configureCell:(EncryptionCell*)cell
[super configureCell:cell]; withStyler:(ChromeTableViewStyler*)styler {
[cell cr_setAccessoryType:self.accessoryType]; [super configureCell:cell withStyler:styler];
cell.accessoryType = self.accessoryType;
cell.textLabel.text = self.text; cell.textLabel.text = self.text;
cell.textLabel.textColor = cell.textLabel.textColor =
[EncryptionCell defaultTextColorForEnabledState:self.enabled]; self.enabled ? [UIColor blackColor]
: UIColorFromRGB(kTableViewSecondaryLabelLightGrayTextColor);
} }
@end @end
...@@ -60,70 +44,38 @@ const CGFloat kVerticalPadding = 16; ...@@ -60,70 +44,38 @@ const CGFloat kVerticalPadding = 16;
@synthesize textLabel = _textLabel; @synthesize textLabel = _textLabel;
+ (UIColor*)defaultTextColorForEnabledState:(BOOL)enabled { - (instancetype)initWithStyle:(UITableViewCellStyle)style
MDCPalette* grey = [MDCPalette greyPalette]; reuseIdentifier:(NSString*)reuseIdentifier {
return enabled ? grey.tint900 : grey.tint500; self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { if (self) {
self.isAccessibilityElement = YES; self.isAccessibilityElement = YES;
_textLabel = [[UILabel alloc] init]; _textLabel = [[UILabel alloc] init];
_textLabel.translatesAutoresizingMaskIntoConstraints = NO; _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
_textLabel.numberOfLines = 0; _textLabel.numberOfLines = 0;
_textLabel.font = [UIFont systemFontOfSize:kUIKitMainFontSize]; _textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
_textLabel.textColor = UIColorFromRGB(kUIKitMainTextColor); _textLabel.adjustsFontForContentSizeCategory = YES;
[self.contentView addSubview:_textLabel]; [self.contentView addSubview:_textLabel];
// Set up the constraints. // Set up the constraints.
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
[_textLabel.leadingAnchor [_textLabel.leadingAnchor
constraintEqualToAnchor:self.contentView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor
constant:kHorizontalPadding], constant:kTableViewHorizontalSpacing],
[_textLabel.trailingAnchor [_textLabel.trailingAnchor
constraintEqualToAnchor:self.contentView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor
constant:-kHorizontalPadding], constant:-kTableViewHorizontalSpacing],
]]; ]];
AddOptionalVerticalPadding(self.contentView, _textLabel, kVerticalPadding); AddOptionalVerticalPadding(self.contentView, _textLabel,
kTableViewLargeVerticalSpacing);
} }
return self; return self;
} }
- (void)layoutSubviews {
// When the accessory type is None, the content view of the cell (and thus)
// the labels inside it span larger than when there is a Checkmark accessory
// type. That means that toggling the accessory type can induce a rewrapping
// of the detail text, which is not visually pleasing. To alleviate that
// issue, always lay out the cell as if there was a Checkmark accessory type.
//
// Force the accessory type to Checkmark for the duration of layout.
MDCCollectionViewCellAccessoryType realAccessoryType = self.accessoryType;
self.accessoryType = MDCCollectionViewCellAccessoryCheckmark;
// Implement -layoutSubviews as per instructions in documentation for
// +[MDCCollectionViewCell cr_preferredHeightForWidth:forItem:].
[super layoutSubviews];
// Adjust the text label preferredMaxLayoutWidth when the parent's width
// changes, for instance on screen rotation.
_textLabel.preferredMaxLayoutWidth =
CGRectGetWidth(self.contentView.frame) - 2 * kHorizontalPadding;
// Re-layout with the new preferred width to allow the label to adjust its
// height.
[super layoutSubviews];
// Restore the real accessory type at the end of the layout.
self.accessoryType = realAccessoryType;
}
- (void)prepareForReuse { - (void)prepareForReuse {
[super prepareForReuse]; [super prepareForReuse];
self.accessoryType = MDCCollectionViewCellAccessoryNone; self.accessoryType = UITableViewCellAccessoryNone;
self.textLabel.text = nil; self.textLabel.text = nil;
[EncryptionCell defaultTextColorForEnabledState:YES];
} }
#pragma mark - UIAccessibility #pragma mark - UIAccessibility
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#import "ios/chrome/browser/ui/settings/cells/encryption_item.h" #import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h" #import "testing/gtest_mac.h"
#include "testing/platform_test.h" #include "testing/platform_test.h"
...@@ -28,12 +29,12 @@ TEST_F(EncryptionItemTest, ConfigureCell) { ...@@ -28,12 +29,12 @@ TEST_F(EncryptionItemTest, ConfigureCell) {
item.text = text; item.text = text;
item.enabled = NO; item.enabled = NO;
item.accessoryType = MDCCollectionViewCellAccessoryCheckmark; item.accessoryType = UITableViewCellAccessoryCheckmark;
[item configureCell:cell]; [item configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]];
EXPECT_NSEQ(text, cell.textLabel.text); EXPECT_NSEQ(text, cell.textLabel.text);
EXPECT_NE(enabledColor, cell.textLabel.textColor); EXPECT_NE(enabledColor, cell.textLabel.textColor);
EXPECT_EQ(MDCCollectionViewCellAccessoryCheckmark, cell.accessoryType); EXPECT_EQ(UITableViewCellAccessoryCheckmark, cell.accessoryType);
} }
} // namespace } // namespace
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
#import "ios/chrome/browser/ui/settings/google_services_settings_local_commands.h" #import "ios/chrome/browser/ui/settings/google_services_settings_local_commands.h"
#import "ios/chrome/browser/ui/settings/google_services_settings_mediator.h" #import "ios/chrome/browser/ui/settings/google_services_settings_mediator.h"
#import "ios/chrome/browser/ui/settings/google_services_settings_view_controller.h" #import "ios/chrome/browser/ui/settings/google_services_settings_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_table_view_controller.h"
#include "ios/chrome/browser/unified_consent/unified_consent_service_factory.h" #include "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/public/provider/chrome/browser/signin/chrome_identity_browser_opener.h" #import "ios/public/provider/chrome/browser/signin/chrome_identity_browser_opener.h"
...@@ -168,14 +168,14 @@ ...@@ -168,14 +168,14 @@
- (void)openEncryptionDialog { - (void)openEncryptionDialog {
browser_sync::ProfileSyncService* syncService = browser_sync::ProfileSyncService* syncService =
ProfileSyncServiceFactory::GetForBrowserState(self.browserState); ProfileSyncServiceFactory::GetForBrowserState(self.browserState);
SettingsRootCollectionViewController* controllerToPush; UIViewController<SettingsRootViewControlling>* controllerToPush;
// If there was a sync error, prompt the user to enter the passphrase. // If there was a sync error, prompt the user to enter the passphrase.
// Otherwise, show the full encryption options. // Otherwise, show the full encryption options.
if (syncService->IsPassphraseRequired()) { if (syncService->IsPassphraseRequired()) {
controllerToPush = [[SyncEncryptionPassphraseCollectionViewController alloc] controllerToPush = [[SyncEncryptionPassphraseCollectionViewController alloc]
initWithBrowserState:self.browserState]; initWithBrowserState:self.browserState];
} else { } else {
controllerToPush = [[SyncEncryptionCollectionViewController alloc] controllerToPush = [[SyncEncryptionTableViewController alloc]
initWithBrowserState:self.browserState]; initWithBrowserState:self.browserState];
} }
controllerToPush.dispatcher = self.dispatcher; controllerToPush.dispatcher = self.dispatcher;
......
...@@ -33,7 +33,6 @@ ...@@ -33,7 +33,6 @@
#import "ios/chrome/browser/ui/settings/cells/account_signin_item.h" #import "ios/chrome/browser/ui/settings/cells/account_signin_item.h"
#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h" #import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
#import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h" #import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
#import "ios/chrome/browser/ui/settings/cells/import_data_multiline_detail_item.h" #import "ios/chrome/browser/ui/settings/cells/import_data_multiline_detail_item.h"
#import "ios/chrome/browser/ui/settings/cells/legacy/legacy_autofill_data_item.h" #import "ios/chrome/browser/ui/settings/cells/legacy/legacy_autofill_data_item.h"
#import "ios/chrome/browser/ui/settings/cells/legacy/legacy_settings_detail_item.h" #import "ios/chrome/browser/ui/settings/cells/legacy/legacy_settings_detail_item.h"
...@@ -112,8 +111,6 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -112,8 +111,6 @@ typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeAutofillStatus, ItemTypeAutofillStatus,
ItemTypeAccountControlDynamicHeight, ItemTypeAccountControlDynamicHeight,
ItemTypeFooter, ItemTypeFooter,
ItemTypeSyncEncryption,
ItemTypeSyncEncryptionChecked,
ItemTypeSyncPassphraseError, ItemTypeSyncPassphraseError,
ItemTypeContentSuggestions, ItemTypeContentSuggestions,
ItemTypeImageDetailTextItem, ItemTypeImageDetailTextItem,
...@@ -407,10 +404,6 @@ const CGFloat kCardIssuerNetworkIconDimension = 25.0; ...@@ -407,10 +404,6 @@ const CGFloat kCardIssuerNetworkIconDimension = 25.0;
// Sync cells. // Sync cells.
[model addSectionWithIdentifier:SectionIdentifierSync]; [model addSectionWithIdentifier:SectionIdentifierSync];
[model addItem:[self syncEncryptionItem]
toSectionWithIdentifier:SectionIdentifierSync];
[model addItem:[self syncEncryptionCheckedItem]
toSectionWithIdentifier:SectionIdentifierSync];
[model addItem:[self syncPassphraseErrorItem] [model addItem:[self syncPassphraseErrorItem]
toSectionWithIdentifier:SectionIdentifierSync]; toSectionWithIdentifier:SectionIdentifierSync];
...@@ -469,8 +462,6 @@ const CGFloat kCardIssuerNetworkIconDimension = 25.0; ...@@ -469,8 +462,6 @@ const CGFloat kCardIssuerNetworkIconDimension = 25.0;
case ItemTypeAutofillDynamicHeight: case ItemTypeAutofillDynamicHeight:
case ItemTypeColdStateSigninPromo: case ItemTypeColdStateSigninPromo:
case ItemTypeWarmStateSigninPromo: case ItemTypeWarmStateSigninPromo:
case ItemTypeSyncEncryption:
case ItemTypeSyncEncryptionChecked:
return [MDCCollectionViewCell return [MDCCollectionViewCell
cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds) cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
forItem:item]; forItem:item];
...@@ -846,27 +837,6 @@ const CGFloat kCardIssuerNetworkIconDimension = 25.0; ...@@ -846,27 +837,6 @@ const CGFloat kCardIssuerNetworkIconDimension = 25.0;
return footerItem; return footerItem;
} }
- (EncryptionItem*)syncEncryptionItem {
EncryptionItem* item =
[[EncryptionItem alloc] initWithType:ItemTypeSyncEncryption];
item.text =
@"These two cells have exactly the same text, but one has a checkmark "
@"and the other does not. They should lay out identically, and the "
@"presence of the checkmark should not cause the text to reflow.";
return item;
}
- (EncryptionItem*)syncEncryptionCheckedItem {
EncryptionItem* item =
[[EncryptionItem alloc] initWithType:ItemTypeSyncEncryptionChecked];
item.text =
@"These two cells have exactly the same text, but one has a checkmark "
@"and the other does not. They should lay out identically, and the "
@"presence of the checkmark should not cause the text to reflow.";
item.accessoryType = MDCCollectionViewCellAccessoryCheckmark;
return item;
}
- (PassphraseErrorItem*)syncPassphraseErrorItem { - (PassphraseErrorItem*)syncPassphraseErrorItem {
PassphraseErrorItem* item = PassphraseErrorItem* item =
[[PassphraseErrorItem alloc] initWithType:ItemTypeSyncPassphraseError]; [[PassphraseErrorItem alloc] initWithType:ItemTypeSyncPassphraseError];
......
...@@ -2,26 +2,26 @@ ...@@ -2,26 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_SYNC_ENCRYPTION_COLLECTION_VIEW_CONTROLLER_H_ #ifndef IOS_CHROME_BROWSER_UI_SETTINGS_SYNC_ENCRYPTION_TABLE_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_SETTINGS_SYNC_ENCRYPTION_COLLECTION_VIEW_CONTROLLER_H_ #define IOS_CHROME_BROWSER_UI_SETTINGS_SYNC_ENCRYPTION_TABLE_VIEW_CONTROLLER_H_
#import "ios/chrome/browser/ui/settings/settings_root_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
namespace ios { namespace ios {
class ChromeBrowserState; class ChromeBrowserState;
} // namespace ios } // namespace ios
// Controller to allow user to specify encryption passphrase for Sync. // Controller to allow user to specify encryption passphrase for Sync.
@interface SyncEncryptionCollectionViewController @interface SyncEncryptionTableViewController : SettingsRootTableViewController
: SettingsRootCollectionViewController
// Designated initializer. |browserState| must not be nil. // Designated initializer. |browserState| must not be nil.
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
NS_DESIGNATED_INITIALIZER; NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithLayout:(UICollectionViewLayout*)layout - (instancetype)initWithTableViewStyle:(UITableViewStyle)style
style:(CollectionViewControllerStyle)style appBarStyle:
(ChromeTableViewControllerStyle)appBarStyle
NS_UNAVAILABLE; NS_UNAVAILABLE;
@end @end
#endif // IOS_CHROME_BROWSER_UI_SETTINGS_SYNC_ENCRYPTION_COLLECTION_VIEW_CONTROLLER_H_ #endif // IOS_CHROME_BROWSER_UI_SETTINGS_SYNC_ENCRYPTION_TABLE_VIEW_CONTROLLER_H_
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#import "ios/chrome/browser/ui/settings/sync_encryption_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/sync_encryption_table_view_controller.h"
#include <memory> #include <memory>
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "components/browser_sync/profile_sync_service.h" #include "components/browser_sync/profile_sync_service.h"
#include "components/google/core/common/google_util.h" #include "components/google/core/common/google_util.h"
...@@ -16,14 +17,14 @@ ...@@ -16,14 +17,14 @@
#include "ios/chrome/browser/chrome_url_constants.h" #include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/sync/profile_sync_service_factory.h" #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
#import "ios/chrome/browser/sync/sync_observer_bridge.h" #import "ios/chrome/browser/sync/sync_observer_bridge.h"
#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
#import "ios/chrome/browser/ui/settings/cells/encryption_item.h" #import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
#import "ios/chrome/browser/ui/settings/settings_utils.h" #import "ios/chrome/browser/ui/settings/settings_utils.h"
#import "ios/chrome/browser/ui/settings/sync_create_passphrase_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/sync_create_passphrase_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
#import "ios/chrome/browser/ui/table_view/table_view_model.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util_mac.h" #include "ui/base/l10n/l10n_util_mac.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -36,7 +37,6 @@ namespace { ...@@ -36,7 +37,6 @@ namespace {
typedef NS_ENUM(NSInteger, SectionIdentifier) { typedef NS_ENUM(NSInteger, SectionIdentifier) {
SectionIdentifierEncryption = kSectionIdentifierEnumZero, SectionIdentifierEncryption = kSectionIdentifierEnumZero,
SectionIdentifierFooter,
}; };
typedef NS_ENUM(NSInteger, ItemType) { typedef NS_ENUM(NSInteger, ItemType) {
...@@ -47,28 +47,20 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -47,28 +47,20 @@ typedef NS_ENUM(NSInteger, ItemType) {
} // namespace } // namespace
@interface SyncEncryptionCollectionViewController ()<SyncObserverModelBridge> { @interface SyncEncryptionTableViewController () <SyncObserverModelBridge> {
ios::ChromeBrowserState* _browserState; ios::ChromeBrowserState* _browserState;
std::unique_ptr<SyncObserverBridge> _syncObserver; std::unique_ptr<SyncObserverBridge> _syncObserver;
BOOL _isUsingSecondaryPassphrase; BOOL _isUsingSecondaryPassphrase;
} }
// Returns an account item.
- (CollectionViewItem*)accountItem;
// Returns a passphrase item.
- (CollectionViewItem*)passphraseItem;
// Returns a footer item with a link.
- (CollectionViewItem*)footerItem;
@end @end
@implementation SyncEncryptionCollectionViewController @implementation SyncEncryptionTableViewController
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState { - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
DCHECK(browserState); DCHECK(browserState);
UICollectionViewLayout* layout = [[MDCCollectionViewFlowLayout alloc] init];
self = self =
[super initWithLayout:layout style:CollectionViewControllerStyleAppBar]; [super initWithTableViewStyle:UITableViewStyleGrouped
appBarStyle:ChromeTableViewControllerStyleWithAppBar];
if (self) { if (self) {
self.title = l10n_util::GetNSString(IDS_IOS_SYNC_ENCRYPTION_TITLE); self.title = l10n_util::GetNSString(IDS_IOS_SYNC_ENCRYPTION_TITLE);
_browserState = browserState; _browserState = browserState;
...@@ -77,18 +69,22 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -77,18 +69,22 @@ typedef NS_ENUM(NSInteger, ItemType) {
_isUsingSecondaryPassphrase = syncService->IsEngineInitialized() && _isUsingSecondaryPassphrase = syncService->IsEngineInitialized() &&
syncService->IsUsingSecondaryPassphrase(); syncService->IsUsingSecondaryPassphrase();
_syncObserver = std::make_unique<SyncObserverBridge>(self, syncService); _syncObserver = std::make_unique<SyncObserverBridge>(self, syncService);
// TODO(crbug.com/764578): -loadModel should not be called from
// initializer. A possible fix is to move this call to -viewDidLoad.
[self loadModel];
} }
return self; return self;
} }
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedSectionFooterHeight =
kTableViewHeaderFooterViewHeight;
[self loadModel];
}
#pragma mark - SettingsRootCollectionViewController #pragma mark - SettingsRootCollectionViewController
- (void)loadModel { - (void)loadModel {
[super loadModel]; [super loadModel];
CollectionViewModel* model = self.collectionViewModel; TableViewModel* model = self.tableViewModel;
[model addSectionWithIdentifier:SectionIdentifierEncryption]; [model addSectionWithIdentifier:SectionIdentifierEncryption];
[model addItem:[self accountItem] [model addItem:[self accountItem]
...@@ -97,15 +93,15 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -97,15 +93,15 @@ typedef NS_ENUM(NSInteger, ItemType) {
toSectionWithIdentifier:SectionIdentifierEncryption]; toSectionWithIdentifier:SectionIdentifierEncryption];
if (_isUsingSecondaryPassphrase) { if (_isUsingSecondaryPassphrase) {
[model addSectionWithIdentifier:SectionIdentifierFooter]; [model setFooter:[self footerItem]
[model addItem:[self footerItem] forSectionWithIdentifier:SectionIdentifierEncryption];
toSectionWithIdentifier:SectionIdentifierFooter];
} }
} }
#pragma mark - Items #pragma mark - Items
- (CollectionViewItem*)accountItem { // Returns an account item.
- (TableViewItem*)accountItem {
DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag()); DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag());
NSString* text = l10n_util::GetNSString(IDS_SYNC_BASIC_ENCRYPTION_DATA); NSString* text = l10n_util::GetNSString(IDS_SYNC_BASIC_ENCRYPTION_DATA);
return [self itemWithType:ItemTypeAccount return [self itemWithType:ItemTypeAccount
...@@ -114,7 +110,8 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -114,7 +110,8 @@ typedef NS_ENUM(NSInteger, ItemType) {
enabled:!_isUsingSecondaryPassphrase]; enabled:!_isUsingSecondaryPassphrase];
} }
- (CollectionViewItem*)passphraseItem { // Returns a passphrase item.
- (TableViewItem*)passphraseItem {
DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag()); DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag());
NSString* text = l10n_util::GetNSString(IDS_SYNC_FULL_ENCRYPTION_DATA); NSString* text = l10n_util::GetNSString(IDS_SYNC_FULL_ENCRYPTION_DATA);
return [self itemWithType:ItemTypePassphrase return [self itemWithType:ItemTypePassphrase
...@@ -123,69 +120,53 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -123,69 +120,53 @@ typedef NS_ENUM(NSInteger, ItemType) {
enabled:!_isUsingSecondaryPassphrase]; enabled:!_isUsingSecondaryPassphrase];
} }
- (CollectionViewItem*)footerItem { // Returns a footer item with a link.
CollectionViewFooterItem* footerItem = - (TableViewHeaderFooterItem*)footerItem {
[[CollectionViewFooterItem alloc] initWithType:ItemTypeFooter]; TableViewLinkHeaderFooterItem* footerItem =
footerItem.cellStyle = CollectionViewCellStyle::kUIKit; [[TableViewLinkHeaderFooterItem alloc] initWithType:ItemTypeFooter];
footerItem.text = footerItem.text =
l10n_util::GetNSString(IDS_IOS_SYNC_ENCRYPTION_PASSPHRASE_HINT); l10n_util::GetNSString(IDS_IOS_SYNC_ENCRYPTION_PASSPHRASE_HINT);
footerItem.linkURL = google_util::AppendGoogleLocaleParam( footerItem.linkURL = google_util::AppendGoogleLocaleParam(
GURL(kSyncGoogleDashboardURL), GURL(kSyncGoogleDashboardURL),
GetApplicationContext()->GetApplicationLocale()); GetApplicationContext()->GetApplicationLocale());
footerItem.linkDelegate = self;
return footerItem; return footerItem;
} }
#pragma mark - MDCCollectionViewStylingDelegate #pragma mark - UITableViewDelegate
- (MDCCollectionViewCellStyle)collectionView:(UICollectionView*)collectionView - (UIView*)tableView:(UITableView*)tableView
cellStyleForSection:(NSInteger)section { viewForFooterInSection:(NSInteger)section {
NSInteger sectionIdentifier = UIView* footerView = [super tableView:tableView
[self.collectionViewModel sectionIdentifierForSection:section]; viewForFooterInSection:section];
switch (sectionIdentifier) { if (SectionIdentifierEncryption ==
case SectionIdentifierFooter: [self.tableViewModel sectionIdentifierForSection:section] &&
// Display the Learn More footer in the default style with no "card" UI [self.tableViewModel footerForSection:section]) {
// and no section padding. TableViewLinkHeaderFooterView* footer =
return MDCCollectionViewCellStyleDefault; base::mac::ObjCCastStrict<TableViewLinkHeaderFooterView>(footerView);
default: footer.delegate = self;
return self.styler.cellStyle;
} }
return footerView;
} }
- (BOOL)collectionView:(UICollectionView*)collectionView - (BOOL)tableView:(UITableView*)tableView
shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath { shouldHighlightRowAtIndexPath:(NSIndexPath*)indexPath {
NSInteger sectionIdentifier = TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
[self.collectionViewModel sectionIdentifierForSection:indexPath.section]; if (item.type == ItemTypePassphrase || item.type == ItemTypeAccount) {
switch (sectionIdentifier) { EncryptionItem* encryptionItem =
case SectionIdentifierFooter: base::mac::ObjCCastStrict<EncryptionItem>(item);
// Display the Learn More footer without any background image or // Don't perform any action if the cell isn't enabled.
// shadowing. return encryptionItem.isEnabled;
return YES;
default:
return NO;
} }
return YES;
} }
- (CGFloat)collectionView:(UICollectionView*)collectionView - (void)tableView:(UITableView*)tableView
cellHeightAtIndexPath:(NSIndexPath*)indexPath { didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
CollectionViewItem* item =
[self.collectionViewModel itemAtIndexPath:indexPath];
return [MDCCollectionViewCell
cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
forItem:item];
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView*)collectionView
didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
[super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
DCHECK_EQ(indexPath.section, DCHECK_EQ(indexPath.section,
[self.collectionViewModel [self.tableViewModel
sectionForSectionIdentifier:SectionIdentifierEncryption]); sectionForSectionIdentifier:SectionIdentifierEncryption]);
CollectionViewItem* item = TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
[self.collectionViewModel itemAtIndexPath:indexPath];
if ([item respondsToSelector:@selector(isEnabled)] && if ([item respondsToSelector:@selector(isEnabled)] &&
![item performSelector:@selector(isEnabled)]) { ![item performSelector:@selector(isEnabled)]) {
// Don't perform any action if the cell isn't enabled. // Don't perform any action if the cell isn't enabled.
...@@ -211,6 +192,8 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -211,6 +192,8 @@ typedef NS_ENUM(NSInteger, ItemType) {
default: default:
break; break;
} }
[tableView deselectRowAtIndexPath:indexPath animated:NO];
} }
#pragma mark SyncObserverModelBridge #pragma mark SyncObserverModelBridge
...@@ -228,14 +211,14 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -228,14 +211,14 @@ typedef NS_ENUM(NSInteger, ItemType) {
#pragma mark - Private methods #pragma mark - Private methods
- (CollectionViewItem*)itemWithType:(NSInteger)type - (TableViewItem*)itemWithType:(NSInteger)type
text:(NSString*)text text:(NSString*)text
checked:(BOOL)checked checked:(BOOL)checked
enabled:(BOOL)enabled { enabled:(BOOL)enabled {
EncryptionItem* item = [[EncryptionItem alloc] initWithType:type]; EncryptionItem* item = [[EncryptionItem alloc] initWithType:type];
item.text = text; item.text = text;
item.accessoryType = checked ? MDCCollectionViewCellAccessoryCheckmark item.accessoryType = checked ? UITableViewCellAccessoryCheckmark
: MDCCollectionViewCellAccessoryNone; : UITableViewCellAccessoryNone;
item.enabled = enabled; item.enabled = enabled;
return item; return item;
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#import "ios/chrome/browser/ui/settings/sync_encryption_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/sync_encryption_table_view_controller.h"
#include <memory> #include <memory>
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/chrome/browser/sync/ios_chrome_profile_sync_test_util.h" #include "ios/chrome/browser/sync/ios_chrome_profile_sync_test_util.h"
#include "ios/chrome/browser/sync/profile_sync_service_factory.h" #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
#import "ios/chrome/browser/ui/collection_view/collection_view_controller_test.h"
#import "ios/chrome/browser/ui/settings/cells/encryption_item.h" #import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
#import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
#include "ios/web/public/test/test_web_thread_bundle.h" #include "ios/web/public/test/test_web_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -39,8 +39,8 @@ std::unique_ptr<KeyedService> CreateNiceProfileSyncServiceMock( ...@@ -39,8 +39,8 @@ std::unique_ptr<KeyedService> CreateNiceProfileSyncServiceMock(
&init_params); &init_params);
} }
class SyncEncryptionCollectionViewControllerTest class SyncEncryptionTableViewControllerTest
: public CollectionViewControllerTest { : public ChromeTableViewControllerTest {
protected: protected:
void SetUp() override { void SetUp() override {
TestChromeBrowserState::Builder test_cbs_builder; TestChromeBrowserState::Builder test_cbs_builder;
...@@ -48,7 +48,7 @@ class SyncEncryptionCollectionViewControllerTest ...@@ -48,7 +48,7 @@ class SyncEncryptionCollectionViewControllerTest
ProfileSyncServiceFactory::GetInstance(), ProfileSyncServiceFactory::GetInstance(),
base::BindRepeating(&CreateNiceProfileSyncServiceMock)); base::BindRepeating(&CreateNiceProfileSyncServiceMock));
chrome_browser_state_ = test_cbs_builder.Build(); chrome_browser_state_ = test_cbs_builder.Build();
CollectionViewControllerTest::SetUp(); ChromeTableViewControllerTest::SetUp();
mock_profile_sync_service_ = mock_profile_sync_service_ =
static_cast<browser_sync::ProfileSyncServiceMock*>( static_cast<browser_sync::ProfileSyncServiceMock*>(
...@@ -62,8 +62,8 @@ class SyncEncryptionCollectionViewControllerTest ...@@ -62,8 +62,8 @@ class SyncEncryptionCollectionViewControllerTest
CreateController(); CreateController();
} }
CollectionViewController* InstantiateController() override { ChromeTableViewController* InstantiateController() override {
return [[SyncEncryptionCollectionViewController alloc] return [[SyncEncryptionTableViewController alloc]
initWithBrowserState:chrome_browser_state_.get()]; initWithBrowserState:chrome_browser_state_.get()];
} }
...@@ -73,20 +73,20 @@ class SyncEncryptionCollectionViewControllerTest ...@@ -73,20 +73,20 @@ class SyncEncryptionCollectionViewControllerTest
browser_sync::ProfileSyncServiceMock* mock_profile_sync_service_; browser_sync::ProfileSyncServiceMock* mock_profile_sync_service_;
}; };
TEST_F(SyncEncryptionCollectionViewControllerTest, TestModel) { TEST_F(SyncEncryptionTableViewControllerTest, TestModel) {
CheckController(); CheckController();
CheckTitleWithId(IDS_IOS_SYNC_ENCRYPTION_TITLE); CheckTitleWithId(IDS_IOS_SYNC_ENCRYPTION_TITLE);
EXPECT_EQ(2, NumberOfSections()); EXPECT_EQ(1, NumberOfSections());
NSInteger const kSection = 0; NSInteger const kSection = 0;
EXPECT_EQ(2, NumberOfItemsInSection(kSection)); EXPECT_EQ(2, NumberOfItemsInSection(kSection));
EncryptionItem* accountItem = GetCollectionViewItem(kSection, 0); EncryptionItem* accountItem = GetTableViewItem(kSection, 0);
EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_BASIC_ENCRYPTION_DATA), EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_BASIC_ENCRYPTION_DATA),
accountItem.text); accountItem.text);
EncryptionItem* passphraseItem = GetCollectionViewItem(kSection, 1); EncryptionItem* passphraseItem = GetTableViewItem(kSection, 1);
EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_FULL_ENCRYPTION_DATA), EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_FULL_ENCRYPTION_DATA),
passphraseItem.text); passphraseItem.text);
} }
......
...@@ -43,8 +43,8 @@ ...@@ -43,8 +43,8 @@
#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h" #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
#import "ios/chrome/browser/ui/settings/cells/text_and_error_item.h" #import "ios/chrome/browser/ui/settings/cells/text_and_error_item.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h" #import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_encryption_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h" #import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h" #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
...@@ -766,14 +766,14 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -766,14 +766,14 @@ typedef NS_ENUM(NSInteger, ItemType) {
[self shouldDisableSettingsOnSyncError]) [self shouldDisableSettingsOnSyncError])
return; return;
SettingsRootCollectionViewController* controllerToPush; UIViewController<SettingsRootViewControlling>* controllerToPush;
// If there was a sync error, prompt the user to enter the passphrase. // If there was a sync error, prompt the user to enter the passphrase.
// Otherwise, show the full encryption options. // Otherwise, show the full encryption options.
if (syncService->IsPassphraseRequired()) { if (syncService->IsPassphraseRequired()) {
controllerToPush = [[SyncEncryptionPassphraseCollectionViewController alloc] controllerToPush = [[SyncEncryptionPassphraseCollectionViewController alloc]
initWithBrowserState:_browserState]; initWithBrowserState:_browserState];
} else { } else {
controllerToPush = [[SyncEncryptionCollectionViewController alloc] controllerToPush = [[SyncEncryptionTableViewController alloc]
initWithBrowserState:_browserState]; initWithBrowserState:_browserState];
} }
controllerToPush.dispatcher = self.dispatcher; controllerToPush.dispatcher = self.dispatcher;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h" #import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h"
#import "ios/chrome/browser/ui/settings/cells/autofill_data_item.h" #import "ios/chrome/browser/ui/settings/cells/autofill_data_item.h"
#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
#import "ios/chrome/browser/ui/settings/cells/settings_detail_item.h" #import "ios/chrome/browser/ui/settings/cells/settings_detail_item.h"
#import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h" #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.h" #import "ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.h"
...@@ -45,6 +46,7 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -45,6 +46,7 @@ typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeURLWithSupplementalText, ItemTypeURLWithSupplementalText,
ItemTypeURLWithBadgeImage, ItemTypeURLWithBadgeImage,
ItemTypeTextSettingsDetail, ItemTypeTextSettingsDetail,
ItemTypeEncryption,
ItemTypeLinkFooter, ItemTypeLinkFooter,
ItemTypeDetailText, ItemTypeDetailText,
ItemTypeSettingsSwitch, ItemTypeSettingsSwitch,
...@@ -173,6 +175,25 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -173,6 +175,25 @@ typedef NS_ENUM(NSInteger, ItemType) {
[model addItem:settingsSwitchItem [model addItem:settingsSwitchItem
toSectionWithIdentifier:SectionIdentifierSettings]; toSectionWithIdentifier:SectionIdentifierSettings];
EncryptionItem* encryptionChecked =
[[EncryptionItem alloc] initWithType:ItemTypeEncryption];
encryptionChecked.text =
@"These two cells have exactly the same text, but one has a checkmark "
@"and the other does not. They should lay out identically, and the "
@"presence of the checkmark should not cause the text to reflow.";
encryptionChecked.accessoryType = UITableViewCellAccessoryCheckmark;
[model addItem:encryptionChecked
toSectionWithIdentifier:SectionIdentifierSettings];
EncryptionItem* encryptionUnchecked =
[[EncryptionItem alloc] initWithType:ItemTypeEncryption];
encryptionUnchecked.text =
@"These two cells have exactly the same text, but one has a checkmark "
@"and the other does not. They should lay out identically, and the "
@"presence of the checkmark should not cause the text to reflow.";
[model addItem:encryptionUnchecked
toSectionWithIdentifier:SectionIdentifierSettings];
TableViewLinkHeaderFooterItem* linkFooter = TableViewLinkHeaderFooterItem* linkFooter =
[[TableViewLinkHeaderFooterItem alloc] initWithType:ItemTypeLinkFooter]; [[TableViewLinkHeaderFooterItem alloc] initWithType:ItemTypeLinkFooter];
linkFooter.text = linkFooter.text =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment