Content Developer II at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
129571 stories
·
29 followers

What’s New in SQL Server & Azure SQL | Data Exposed Live @ PASS Summit 2024

1 Share
From: Microsoft Developer
Duration: 57:50
Views: 136

Join the audience of a Data Exposed session LIVE from PASS Summit 2024! You'll receive the latest updates containing all things SQL Server and Azure SQL. This not-to-miss session will include demos, special guests, and a sneak peek at the future!

Read the whole story
alvinashcraft
2 hours ago
reply
West Grove, PA
Share this story
Delete

Meet your two newest learning benefits (and the devs behind them) | Bits & Pieces ep4

1 Share
From: VisualStudio
Duration: 34:18
Views: 248

Get ready for an exciting episode of "Bits and Pieces," the show dedicated to Visual Studio subscribers! This episode features two special guests, Tim Corey and Nick Chapsas, who are renowned for their popular YouTube channels and educational platforms, DevForge and Dometrain.

Join us as we dive into their journey from being Microsoft MVPs to becoming key content creators in the Visual Studio community. Discover what drives them to create premium developer content, how they decide on topics, and the unique approaches they use to gather feedback from their communities. Whether you're a beginner or a seasoned developer, this episode has something for everyone. Tune in to learn more about the incredible resources they offer and how their platforms are now part of the Visual Studio subscription benefits. Don't miss out on this insightful conversation!

Sign in to my.visualstudio.com to explore your Visual Studio subscription benefits today!

Read the whole story
alvinashcraft
2 hours ago
reply
West Grove, PA
Share this story
Delete

Design System from scratch in Flutter

1 Share

Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design System, which we have implemented in our projects before.

Why do we need a Design System?

In our example, we have applied it to share our design code between mobile, web, and desktop apps. As a result, it is an independent package working on its own. We can inject into any project in a few steps.

Let’s start with the atomic parts.

As a first step, we divided all minor parts — colors, radiuses, shadows, etc into independent classes. Definitely, Implemented design system codes depend on the designer’s implementation.

As you can see, the designer divided atomic parts for us (thanks to our designer)

I will not mention all the code; it will be snippets. They are like the following examples:

/// {@template app_colors}
/// Colors class for themes which provides direct access with static fields.
/// {@endtemplate}
class AppColors {
AppColors._();

/// The color white
static const white = Colors.white;

/// The color black
static const black = Colors.black;

/// The color transparent
static const transparent = Colors.transparent;

/// Brand color palette.
static const brand = MaterialColor(
0xFF347AF6,
{
50: Color(0xFFF0F5FF),
100: Color(0xFFE0ECFF),
150: Color(0xFFD3E1FB),
200: Color(0xFFBDD3F9),
250: Color(0xFF9FBFF9),
300: Color(0xFF81ACF9),
400: Color(0xFF5A93F9),
500: Color(0xFF347AF6),
600: Color(0xFF1559D1),
700: Color(0xFF174EAF),
800: Color(0xFF1D4387),
900: Color(0xFF163367),
},
);

/// Light gray color palette.
static const grayLight = MaterialColor(
0xFF667085,
{
50: Color(0xFFFCFCFD),
100: Color(0xFFF9FAFB),
150: Color(0xFFF2F4F7),
200: Color(0xFFEAECF0),
250: Color(0xFFD0D5DD),
300: Color(0xFF98A2B3),
400: Color(0xFF667085),
500: Color(0xFF475467),
600: Color(0xFF344054),
700: Color(0xFF182230),
800: Color(0xFF101828),
900: Color(0xFF0C111D),
},
);

/// Dark gray color palette.
static const grayDark = MaterialColor(
0xFF85888E,
{
50: Color(0xFFFAFAFA),
100: Color(0xFFF5F5F6),
150: Color(0xFFF0F1F1),
200: Color(0xFFECECED),
250: Color(0xFFCECFD2),
300: Color(0xFF94969C),
400: Color(0xFF85888E),
500: Color(0xFF61646C),
600: Color(0xFF333741),
700: Color(0xFF1F242F),
800: Color(0xFF161B26),
900: Color(0xFF0C111D),
},
);
/// {@template app_radius}
/// Radius class contains all radius used in app
/// {@endtemplate}
class AppRadius {
AppRadius._();

/// Radius of 0.
static const none = Radius.zero;

/// Extra extra small radius of 2.
static const xxs = Radius.circular(2);

/// Extra small radius of 4.
static const xs = Radius.circular(4);

/// Small radius of 6.
static const sm = Radius.circular(6);
}
/// {@template app_shadow}
/// Shadow class contains all shadows used in app
/// {@endtemplate}
class AppShadow {
AppShadow._();


/// Extra small shadow.
static const xs = [
BoxShadow(
blurRadius: 2,
offset: Offset(0, 1),
color: Color.fromRGBO(16, 24, 40, 0.05),
),
];

/// Small shadow.
static const sm = [
BoxShadow(
color: Color(0x0F101828),
blurRadius: 2,
offset: Offset(0, 1),
),
BoxShadow(
color: Color(0x19101828),
blurRadius: 3,
offset: Offset(0, 1),
),
];
}
/// {@template app_spacing}
/// Class contains all space (does not matter is it vertical
/// or horizontal used in app
/// {@endtemplate}
class AppSpacing {
AppSpacing._();

/// No spacing.
static const none = 0.0;

/// Extra extra small spacing of 2.0.
static const xxs = 2.0;

/// Extra small spacing of 4.0.
static const xs = 4.0;

/// Small spacing of 6.0.
static const sm = 6.0;
You can have more atomic parts in your projects.

Next stage — components

I will show you some of our components — like buttons, textfield. Other components developed in the same way.

Theme development for component

I have written a new theme class for each of our components to keep them more clean. Sure, it is provided by our designer, too :)

Keep in mind that Theme classes are extended from ThemeExtension, in this way, we can register them as theme extensions and use them with Theme class.

For instance, we can check Theme classes for buttons and text fields:

/// {@template app_button_theme}
/// Theme class which provides configuration of buttons
/// {@endtemplate}
class AppButtonTheme extends ThemeExtension<AppButtonTheme> {
/// {@macro app_button_theme}
const AppButtonTheme({
required this.primaryText,
required this.primaryDefault,
required this.primaryHover,
required this.primaryFocused,
});

/// {@macro app_button_theme}
factory AppButtonTheme.light() {
return AppButtonTheme(
primaryText: AppColors.white,
primaryDefault: AppColors.brand.shade500,
primaryHover: AppColors.brand.shade600,
primaryFocused: AppColors.brand.shade700,
);
}

/// The color of the primary text.
final Color primaryText;

/// The color of the primary button default.
final Color primaryDefault;

/// The color of the primary button hover.
final Color primaryHover;

/// The color of the primary button focused.
final Color primaryFocused;

@override
ThemeExtension<AppButtonTheme> copyWith({
Color? primaryText,
Color? primaryDefault,
Color? primaryHover,
Color? primaryFocused,
}) {
return AppButtonTheme(
primaryText: primaryText ?? this.primaryText,
primaryDefault: primaryDefault ?? this.primaryDefault,
primaryHover: primaryHover ?? this.primaryHover,
primaryFocused: primaryFocused ?? this.primaryFocused,
);
}

@override
ThemeExtension<AppButtonTheme> lerp(
covariant ThemeExtension<AppButtonTheme>? other,
double t,
) {
if (other is! AppButtonTheme) {
return this;
}

return AppButtonTheme(
primaryText: Color.lerp(primaryText, other.primaryText, t)!,
primaryDefault: Color.lerp(primaryDefault, other.primaryDefault, t)!,
primaryHover: Color.lerp(primaryHover, other.primaryHover, t)!,
primaryFocused: Color.lerp(primaryFocused, other.primaryFocused, t)!,
);
}
}
/// {@template app_input_theme}
/// Theme class which provides configuration of [AppTextField]
/// {@endtemplate}
class AppInputTheme extends ThemeExtension<AppInputTheme> {
/// {@macro app_input_theme}
const AppInputTheme({
required this.defaultText,
required this.focusedOnBrand,
required this.focusedTextDefault,
required this.errorTextDefault,
required this.successTextDefault,
required this.disabledText,
required this.borderDefault,
required this.borderHover,
required this.borderFocused,
required this.borderError,
required this.borderSuccess,
required this.borderDisabled,
required this.defaultColor,
required this.disabledColor,
});

/// {@macro app_input_theme}
factory AppInputTheme.light() {
return AppInputTheme(
defaultText: AppColors.grayLight.shade400,
focusedOnBrand: AppColors.brand.shade500,
focusedTextDefault: AppColors.grayLight.shade600,
errorTextDefault: AppColors.error.shade400,
successTextDefault: AppColors.success.shade400,
disabledText: AppColors.grayLight[250]!,
borderDefault: AppColors.grayLight[250]!,
borderHover: AppColors.grayLight.shade300,
borderFocused: AppColors.brand.shade500,
borderError: AppColors.error.shade400,
borderSuccess: AppColors.success.shade400,
borderDisabled: AppColors.grayLight.shade200,
defaultColor: AppColors.white,
disabledColor: AppColors.grayLight.shade100,
);
}

/// The default text color.
final Color defaultText;

/// The text color when focused on brand.
final Color focusedOnBrand;

/// The text color when focused.
final Color focusedTextDefault;

/// The text color when error.
final Color errorTextDefault;

/// The text color when success.
final Color successTextDefault;

/// The text color when disabled.
final Color disabledText;

/// The default border color.
final Color borderDefault;

/// The border color when hovered.
final Color borderHover;

/// The border color when focused.
final Color borderFocused;

/// The border color when error.
final Color borderError;

/// The border color when success.
final Color borderSuccess;

/// The border color when disabled.
final Color borderDisabled;

/// The default color.
final Color defaultColor;

/// The disabled color.
final Color disabledColor;

@override
ThemeExtension<AppInputTheme> copyWith({
Color? defaultText,
Color? focusedOnBrand,
Color? focusedTextDefault,
Color? errorTextDefault,
Color? successTextDefault,
Color? disabledText,
Color? borderDefault,
Color? borderHover,
Color? borderFocused,
Color? borderError,
Color? borderSuccess,
Color? borderDisabled,
Color? defaultColor,
Color? disabledColor,
}) {
return AppInputTheme(
defaultText: defaultText ?? this.defaultText,
focusedOnBrand: focusedOnBrand ?? this.focusedOnBrand,
focusedTextDefault: focusedTextDefault ?? this.focusedTextDefault,
errorTextDefault: errorTextDefault ?? this.errorTextDefault,
successTextDefault: successTextDefault ?? this.successTextDefault,
disabledText: disabledText ?? this.disabledText,
borderDefault: borderDefault ?? this.borderDefault,
borderHover: borderHover ?? this.borderHover,
borderFocused: borderFocused ?? this.borderFocused,
borderError: borderError ?? this.borderError,
borderSuccess: borderSuccess ?? this.borderSuccess,
borderDisabled: borderDisabled ?? this.borderDisabled,
defaultColor: defaultColor ?? this.defaultColor,
disabledColor: disabledColor ?? this.disabledColor,
);
}

@override
ThemeExtension<AppInputTheme> lerp(
covariant ThemeExtension<AppInputTheme>? other,
double t,
) {
if (other is! AppInputTheme) {
return this;
}

return AppInputTheme(
defaultText: Color.lerp(defaultText, other.defaultText, t)!,
focusedOnBrand: Color.lerp(focusedOnBrand, other.focusedOnBrand, t)!,
focusedTextDefault: Color.lerp(
focusedTextDefault,
other.focusedTextDefault,
t,
)!,
errorTextDefault: Color.lerp(
errorTextDefault,
other.errorTextDefault,
t,
)!,
successTextDefault: Color.lerp(
successTextDefault,
other.successTextDefault,
t,
)!,
disabledText: Color.lerp(disabledText, other.disabledText, t)!,
borderDefault: Color.lerp(borderDefault, other.borderDefault, t)!,
borderHover: Color.lerp(borderHover, other.borderHover, t)!,
borderFocused: Color.lerp(borderFocused, other.borderFocused, t)!,
borderError: Color.lerp(borderError, other.borderError, t)!,
borderSuccess: Color.lerp(borderSuccess, other.borderSuccess, t)!,
borderDisabled: Color.lerp(borderDisabled, other.borderDisabled, t)!,
defaultColor: Color.lerp(defaultColor, other.defaultColor, t)!,
disabledColor: Color.lerp(disabledColor, other.disabledColor, t)!,
);
}
}

Moreover, we have an AppTypography class, which collects font sizes as an independent theme.

/// {@template app_typography}
/// Theme class which provides configuration of [TextStyle]
/// {@endtemplate}
interface class AppTypography extends ThemeExtension<AppTypography> {
/// {@macro app_typography}
AppTypography({
required this.buttonLarge,
required this.buttonMedium,
required this.buttonSmall,
});

/// Button Large
final TextStyle buttonLarge;

/// Button Medium
final TextStyle buttonMedium;

/// Button Small
final TextStyle buttonSmall;

@override
ThemeExtension<AppTypography> copyWith({
TextStyle? buttonLarge,
TextStyle? buttonMedium,
TextStyle? buttonSmall,
}) {
return AppTypography(
buttonLarge: buttonLarge ?? this.buttonLarge,
buttonMedium: buttonMedium ?? this.buttonMedium,
buttonSmall: buttonSmall ?? this.buttonSmall,
);
}

@override
ThemeExtension<AppTypography> lerp(
covariant ThemeExtension<AppTypography>? other,
double t,
) {
if (other is! AppTypography) {
return this;
}

return AppTypography(
buttonLarge: TextStyle.lerp(buttonLarge, other.buttonLarge, t)!,
buttonMedium: TextStyle.lerp(buttonMedium, other.buttonMedium, t)!,
buttonSmall: TextStyle.lerp(buttonSmall, other.buttonSmall, t)!,
);
}
}

/// {@macro app_typography}
class AppRegularTypography extends AppTypography {
/// {@macro app_typography}
AppRegularTypography({
super.buttonLarge = const TextStyle(
fontSize: 16,
height: 24 / 16,
fontWeight: FontWeight.w500,
),
super.buttonMedium = const TextStyle(
fontSize: 14,
height: 20 / 14,
fontWeight: FontWeight.w500,
),
super.buttonSmall = const TextStyle(
fontSize: 14,
height: 20 / 14,
fontWeight: FontWeight.w500,
),
),
});
}

After all… Components

Component development depends on the design given by the designer. In reality, all the things we have developed above depend on. In our case, for instance, text buttons have many versions, such as size, decoration, etc.

We had many buttons, but I have shared some of them (text buttons)

Therefore, we have created a base class for our text buttons. Here is the code snippet:

/// {@template app_text_button}
/// A custom text button widget that adapts to the platform.
/// {@endtemplate}
abstract class AppTextButton extends StatelessWidget {
/// {@macro app_text_button}
const AppTextButton({
super.key,
required this.label,
this.onTap,
this.leading,
this.trailing,
this.appButtonSize = AppButtonSize.medium,
});

/// The label for the text button.
final String label;

/// The callback function for the text button.
final VoidCallback? onTap;

/// The leading icon for the text button.
final IconBuilder? leading;

/// The trailing icon for the text button.
final IconBuilder? trailing;

/// The size of the text button.
final AppButtonSize appButtonSize;

/// The background color for the text button.
Color backgroundColor(BuildContext context);

/// The focus color for the text button.
Color focusColor(BuildContext context);

/// The hover color for the text button.
Color hoverColor(BuildContext context);

/// The disabled color for the text button.
Color disabledColor(BuildContext context);

/// The text color for the text button.
Color textColor(BuildContext context);

/// The disabled text color for the text button.
Color disabledTextColor(BuildContext context) {
return context.buttonTheme.primaryTextDisabled;
}

/// The default border for the text button.
BorderSide defaultBorder(BuildContext context) => BorderSide.none;

/// The focused border for the text button.
BorderSide focusedBorder(BuildContext context) => BorderSide.none;

/// The hover border for the text button.
BorderSide hoverBorder(BuildContext context) => BorderSide.none;

/// The disabled border for the text button.
BorderSide disabledBorder(BuildContext context) => BorderSide.none;

@override
Widget build(BuildContext context) {
final betweenSpace = switch (appButtonSize) {
AppButtonSize.small ||
AppButtonSize.xSmall ||
AppButtonSize.medium =>
AppSpacing.xs,
AppButtonSize.large || AppButtonSize.xlarge => AppSpacing.sm,
AppButtonSize.xxLarge => AppSpacing.lg,
};

final inputTextColor = WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.disabled)) {
return disabledTextColor(context);
}

return textColor(context);
},
);

return ElevatedButton(
style: ButtonStyle(
elevation: WidgetStateProperty.all(0),
splashFactory: NoSplash.splashFactory,
overlayColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.disabled)) {
return disabledColor(context);
}

if (states.contains(WidgetState.hovered)) {
return hoverColor(context);
}

if (states.contains(WidgetState.focused)) {
return focusColor(context);
}

if (states.contains(WidgetState.pressed)) {
return focusColor(context);
}

return backgroundColor(context);
},
),
shape: WidgetStateProperty.resolveWith(
(states) {
const shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(AppRadius.md),
);

if (states.contains(WidgetState.disabled)) {
return shape.copyWith(side: disabledBorder(context));
}

if (states.contains(WidgetState.focused)) {
return shape.copyWith(side: focusedBorder(context));
}

if (states.contains(WidgetState.hovered)) {
return shape.copyWith(side: hoverBorder(context));
}

if (states.contains(WidgetState.pressed)) {
return shape.copyWith(side: focusedBorder(context));
}

return shape.copyWith(side: defaultBorder(context));
},
),
backgroundColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.disabled)) {
return disabledColor(context);
}

if (states.contains(WidgetState.hovered)) {
return hoverColor(context);
}

if (states.contains(WidgetState.focused)) {
return focusColor(context);
}

if (states.contains(WidgetState.pressed)) {
return focusColor(context);
}

return backgroundColor(context);
},
),
foregroundColor: inputTextColor,
fixedSize: WidgetStateProperty.all(
switch (appButtonSize) {
AppButtonSize.small ||
AppButtonSize.xSmall =>
const Size(double.infinity, 36),
AppButtonSize.medium => const Size(double.infinity, 40),
AppButtonSize.large => const Size(double.infinity, 44),
AppButtonSize.xlarge => const Size(double.infinity, 48),
AppButtonSize.xxLarge => const Size(double.infinity, 56),
},
),
padding: WidgetStateProperty.all(
switch (appButtonSize) {
AppButtonSize.small ||
AppButtonSize.xSmall =>
const EdgeInsets.symmetric(horizontal: 12),
AppButtonSize.medium => const EdgeInsets.symmetric(horizontal: 16),
AppButtonSize.large => const EdgeInsets.symmetric(horizontal: 16),
AppButtonSize.xlarge => const EdgeInsets.symmetric(horizontal: 20),
AppButtonSize.xxLarge => const EdgeInsets.symmetric(horizontal: 24),
},
),
),
onPressed: onTap,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (leading != null) ...[
leading!(
onTap != null ? textColor(context) : disabledTextColor(context),
),
SizedBox(width: betweenSpace),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xxs),
child: Text(
label,
style: switch (appButtonSize) {
AppButtonSize.small ||
AppButtonSize.xSmall =>
context.typography.buttonSmall,
AppButtonSize.medium => context.typography.buttonMedium,
AppButtonSize.large => context.typography.buttonLarge,
AppButtonSize.xlarge => context.typography.buttonXLarge,
AppButtonSize.xxLarge => context.typography.button2XLarge,
},
),
),
if (trailing != null) ...[
SizedBox(width: betweenSpace),
trailing!(
onTap != null ? textColor(context) : disabledTextColor(context),
),
],
],
),
);
}
}

AppButtonSize is a simple enum provided by us:

/// Enum for button sizes
enum AppButtonSize {
/// Extra small button size
xSmall,

/// Small button size
small,

/// Medium button size
medium,

/// Large button size
large,

/// Extra large button size
xlarge,

/// Extra extra large button size
xxLarge,
}

IconBuilder is a simple typedef provided by us. In some cases, we can need to change the color of an icon within the inner state of the button (focus, hover, and other state colors can be applied to the color of the icon)

/// A function that builds an icon widget.
typedef IconBuilder = Widget Function(Color iconColor);

Eventually, with the help of the base AppTextButton class, we can create our child classes. So, our Primary, Secondary, and Outlined text buttons’ codes will be like the following:

/// {@template primary_text_button}
/// A custom primary text button widget that adapts to the platform.
/// {@endtemplate}
class PrimaryTextButton extends AppTextButton {
/// {@macro primary_text_button}
const PrimaryTextButton({
super.key,
required super.label,
super.onTap,
super.leading,
super.trailing,
super.appButtonSize,
});

@override
Color backgroundColor(BuildContext context) {
return context.buttonTheme.primaryDefault;
}

@override
Color disabledColor(BuildContext context) {
return context.buttonTheme.primaryDisabled;
}

@override
Color focusColor(BuildContext context) {
return context.buttonTheme.primaryFocused;
}

@override
Color hoverColor(BuildContext context) {
return context.buttonTheme.primaryHover;
}

@override
Color textColor(BuildContext context) {
return context.buttonTheme.primaryText;
}
}
/// {@template secondary_text_button}
/// A custom secondary text button widget that adapts to the platform.
/// {@endtemplate}
///
class SecondaryTextButton extends AppTextButton {
/// {@macro secondary_text_button}
const SecondaryTextButton({
super.key,
required super.label,
super.onTap,
super.leading,
super.trailing,
super.appButtonSize,
});

@override
Color backgroundColor(BuildContext context) {
return context.buttonTheme.secondaryDefault;
}

@override
Color disabledColor(BuildContext context) {
return context.buttonTheme.secondaryDisabled;
}

@override
Color focusColor(BuildContext context) {
return context.buttonTheme.secondaryFocused;
}

@override
Color hoverColor(BuildContext context) {
return context.buttonTheme.secondaryHover;
}

@override
Color textColor(BuildContext context) {
return context.buttonTheme.primaryTextOnBrand;
}
}
/// {@template outline_text_button}
/// A custom outline text button widget that adapts to the platform.
/// {@endtemplate}
class OutlineTextButton extends AppTextButton {
/// {@macro outline_text_button}
const OutlineTextButton({
super.key,
required super.label,
super.onTap,
super.leading,
super.trailing,
super.appButtonSize,
});

@override
Color backgroundColor(BuildContext context) {
return context.buttonTheme.outlinedDefault;
}

@override
Color disabledColor(BuildContext context) {
return context.buttonTheme.outlinedDisabled;
}

@override
Color focusColor(BuildContext context) {
return context.buttonTheme.outlinedFocused;
}

@override
Color hoverColor(BuildContext context) {
return context.buttonTheme.outlinedHover;
}

@override
Color textColor(BuildContext context) {
return context.buttonTheme.buttonLineDefault;
}

@override
BorderSide defaultBorder(BuildContext context) {
return BorderSide(
color: context.buttonTheme.buttonLineDefault,
);
}

@override
BorderSide focusedBorder(BuildContext context) {
return BorderSide(
color: context.buttonTheme.buttonLineDefault,
);
}

@override
BorderSide hoverBorder(BuildContext context) {
return BorderSide(
color: context.buttonTheme.buttonLineDefault,
);
}

@override
BorderSide disabledBorder(BuildContext context) {
return BorderSide(
color: context.buttonTheme.outlinedBorderDisabled,
);
}
}

For the text field, we have created again an independent AppTextField class.

/// {@template app_text_field}
/// A customizable text field widget with various customization options.
/// {@endtemplate}
class AppTextField extends StatelessWidget {
/// {@macro app_text_field}
const AppTextField({
super.key,
this.controller,
this.labelText,
this.enabled = true,
this.obscureText = false,
this.onChanged,
this.autovalidateMode = AutovalidateMode.onUserInteraction,
this.validator,
this.helperText,
this.errorText,
this.suffixIcon,
this.suffixIconConstraints =
const BoxConstraints(minHeight: 24, minWidth: 40),
this.prefixIcon,
this.prefixIconConstraints =
const BoxConstraints(minHeight: 24, minWidth: 40),
this.autofillHints,
this.onEditingComplete,
this.inputFormatters,
this.keyboardType,
this.maxLines = 1,
});

/// The controller for the text field.
final TextEditingController? controller;

/// The label text for the text field.
final String? labelText;

/// Whether the text field is enabled.
final bool enabled;

/// Whether the text field is obscured.
final bool obscureText;

/// Called when the text field value changes.
final ValueChanged<String>? onChanged;

/// The autovalidate mode for the text field.
final AutovalidateMode autovalidateMode;

/// The validator for the text field.
final FormFieldValidator<String>? validator;

/// The helper text for the text field.
final String? helperText;

/// The error text for the text field.
final String? errorText;

/// The suffix icon for the text field.
final Widget? suffixIcon;

/// The constraints for the suffix icon.
final BoxConstraints? suffixIconConstraints;

/// The prefix icon for the text field.
final Widget? prefixIcon;

/// The constraints for the prefix icon.
final BoxConstraints? prefixIconConstraints;

/// The autofillhints for app text field.
final Iterable<String>? autofillHints;

/// Called when the text field value completed.
final VoidCallback? onEditingComplete;

/// The input formatters for the text field.
final List<TextInputFormatter>? inputFormatters;

/// The keyboard type for the text field.
final TextInputType? keyboardType;

/// the maximum lines available in text field.
final int maxLines;

@override
Widget build(BuildContext context) {
return TextFormField(
keyboardType: keyboardType,
inputFormatters: inputFormatters,
onEditingComplete: onEditingComplete,
autofillHints: autofillHints,
controller: controller,
enabled: enabled,
obscureText: obscureText,
onChanged: onChanged,
autovalidateMode: autovalidateMode,
validator: validator,
maxLines: maxLines,
style: WidgetStateTextStyle.resolveWith(
(states) {
late final Color textColor;

if (states.contains(WidgetState.error)) {
textColor = context.inputTheme.focusedTextDefault;
} else if (states.contains(WidgetState.focused)) {
textColor = context.inputTheme.focusedTextDefault;
} else if (states.contains(WidgetState.disabled)) {
textColor = context.inputTheme.disabledText;
} else {
textColor = context.inputTheme.defaultText;
}

return context.typography.inputPlaceHolder.copyWith(
color: textColor,
);
},
),
cursorColor: context.inputTheme.focusedTextDefault,
cursorHeight: 16,
decoration: InputDecoration(
labelText: labelText,
labelStyle: WidgetStateTextStyle.resolveWith(
(states) {
late final Color textColor;

if (states.contains(WidgetState.error)) {
textColor = context.inputTheme.errorTextDefault;
} else if (states.contains(WidgetState.focused)) {
textColor = context.inputTheme.focusedOnBrand;
} else if (states.contains(WidgetState.disabled)) {
textColor = context.inputTheme.disabledText;
} else {
textColor = context.inputTheme.defaultText;
}

return context.typography.inputPlaceHolder.copyWith(
color: textColor,
);
},
),
floatingLabelStyle: WidgetStateTextStyle.resolveWith(
(states) {
late final Color textColor;

if (states.contains(WidgetState.error)) {
textColor = context.inputTheme.errorTextDefault;
} else if (states.contains(WidgetState.focused)) {
textColor = context.inputTheme.focusedOnBrand;
} else {
textColor = context.inputTheme.defaultText;
}

return context.typography.inputLabel.copyWith(
color: textColor,
);
},
),
filled: true,
fillColor: enabled
? context.inputTheme.defaultColor
: context.inputTheme.disabledColor,
border: MaterialStateOutlineInputBorder.resolveWith(
(states) {
late final Color borderColor;

if (states.contains(WidgetState.error)) {
borderColor = context.inputTheme.borderError;
} else if (states.contains(WidgetState.focused)) {
borderColor = context.inputTheme.borderFocused;
} else if (states.contains(WidgetState.disabled)) {
borderColor = context.inputTheme.borderDisabled;
} else if (states.contains(WidgetState.hovered)) {
borderColor = context.inputTheme.borderHover;
} else {
borderColor = context.inputTheme.borderDefault;
}

return OutlineInputBorder(
borderRadius: const BorderRadius.all(AppRadius.md),
borderSide: BorderSide(
color: borderColor,
),
);
},
),
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
helperText: helperText,
helperStyle: WidgetStateTextStyle.resolveWith(
(states) {
late final Color textColor;

if (states.contains(WidgetState.error)) {
textColor = context.inputTheme.errorTextDefault;
} else if (states.contains(WidgetState.focused)) {
textColor = context.inputTheme.focusedOnBrand;
} else if (states.contains(WidgetState.disabled)) {
textColor = context.inputTheme.disabledText;
} else {
textColor = context.inputTheme.defaultText;
}

return context.typography.inputHint.copyWith(
color: textColor,
);
},
),
errorText: errorText,
errorStyle: context.typography.inputHint.copyWith(
color: context.inputTheme.errorTextDefault,
),
suffixIcon: suffixIcon,
prefixIcon: prefixIcon,
suffixIconConstraints: suffixIconConstraints,
prefixIconConstraints: prefixIconConstraints,
),
);
}
}

Extension to get themes with context

To get themes with context, we have created a simple extension class:

/// An extension on [BuildContext] that provides access to the current theme.
extension ThemeExt on BuildContext {
/// The current theme.
ThemeData get theme => Theme.of(this);

///the current button theme
AppButtonTheme get buttonTheme =>
theme.extension<AppTheme>()!.appButtonTheme as AppButtonTheme;

/// The current app checkboxTheme.
AppCheckboxTheme get checkboxTheme =>
theme.extension<AppTheme>()!.appCheckboxTheme as AppCheckboxTheme;

/// The current app iconTheme.
AppIconTheme get iconTheme =>
theme.extension<AppTheme>()!.appIconTheme as AppIconTheme;

/// The current app inputTheme.
AppInputTheme get inputTheme =>
theme.extension<AppTheme>()!.appInputTheme as AppInputTheme;

/// The current app radioTheme.
AppRadioTheme get radioTheme =>
theme.extension<AppTheme>()!.appRadioTheme as AppRadioTheme;

/// The current app toggleTheme.
AppToggleTheme get toggleTheme =>
theme.extension<AppTheme>()!.appToggleTheme as AppToggleTheme;

/// The current app typographyTheme.
AppTypographyTheme get typographyTheme =>
theme.extension<AppTheme>()!.appTypographyTheme as AppTypographyTheme;

/// The current app avatarTheme.
AppAvatarTheme get avatarTheme =>
theme.extension<AppTheme>()!.appAvatarTheme as AppAvatarTheme;

/// The current app typography.
AppTypography get typography =>
theme.extension<AppTheme>()!.appTypography as AppTypography;

/// The current app navigationTheme.
AppNavigationTheme get navigationTheme =>
theme.extension<AppTheme>()!.appNavigationTheme as AppNavigationTheme;

/// The current app layoutTheme.
AppLayoutTheme get layoutTheme =>
theme.extension<AppTheme>()!.appLayoutTheme as AppLayoutTheme;

/// The current app badgeTheme.
AppBadgeTheme get badgeTheme =>
theme.extension<AppTheme>()!.appBadgeTheme as AppBadgeTheme;

/// The current app breadcrumbTheme.
AppBreadCrumbTheme get appBreadCrumbTheme =>
theme.extension<AppTheme>()!.appBreadCrumbTheme as AppBreadCrumbTheme;

/// The current app appDropdownTheme.
AppDropdownTheme get appDropdownTheme =>
theme.extension<AppTheme>()!.appDropdownTheme as AppDropdownTheme;
}

So… What is AppTheme there?

AppTheme is a simple, immutable class (ThemeExtension) that provides all themes to our application.

/// {@template app_theme}
/// Configuration class which collects all Themes of app together and provides
/// them as a single instance
/// {@endtemplate}
class AppTheme extends ThemeExtension<AppTheme> {
/// {@macro app_theme}
const AppTheme({
required this.appButtonTheme,
required this.appInputTheme,
});

/// {@macro app_theme}
factory AppTheme.light() {
return AppTheme(
appButtonTheme: AppButtonTheme.light(),
appInputTheme: AppInputTheme.light(),
);
}

/// [AppButtonTheme] instance provides configuration of buttons
final ThemeExtension<AppButtonTheme> appButtonTheme;

/// [AppInputTheme] instance provides configuration of [AppTextField]
final ThemeExtension<AppInputTheme> appInputTheme;


@override
ThemeExtension<AppTheme> copyWith({
ThemeExtension<AppButtonTheme>? appButtonTheme,
ThemeExtension<AppInputTheme>? appInputTheme,
}) {
return AppTheme(
appButtonTheme: appButtonTheme ?? this.appButtonTheme,
appInputTheme: appInputTheme ?? this.appInputTheme,
);
}

@override
ThemeExtension<AppTheme> lerp(
covariant ThemeExtension<AppTheme>? other,
double t,
) {
if (other is! AppTheme) {
return this;
}

return AppTheme(
appButtonTheme: appButtonTheme.lerp(other.appButtonTheme, t),
appInputTheme: appInputTheme.lerp(other.appInputTheme, t),
);
}
}

How will we provide our theme for a new project?

It is not a difficult process. If you know about InheritedWidget, you can understand this step easily.

Therefore, we created a simple InheritedWidget called ThemeScope to provide our design system for a new project.

/// {@template theme_scope}
/// InheritedWidget provides [AppTheme] for app
/// {@endtemplate}
class ThemeScope extends InheritedWidget {
/// {@macro theme_scope}
const ThemeScope({
super.key,
required Widget child,
required this.themeMode,
required this.appTheme,
}) : super(child: child);

/// The current theme mode.
final ThemeMode themeMode;

/// The current app theme.
final AppTheme appTheme;

/// The current theme.
static ThemeScope of(BuildContext context) {
final result = context.dependOnInheritedWidgetOfExactType<ThemeScope>();
assert(result != null, 'No ThemeScope found in context');
return result!;
}

@override
bool updateShouldNotify(ThemeScope oldWidget) => true;
}

ThemeMode handler

For handling the dark, light, and system mode switching process, your can write a simple controller and initializer as the following:

const _kThemeMode = 'themeMode';

/// {@template theme_scope_widget}
/// A class which handles all theme processes
///
/// initialize() method should be used as app starter in order to use
/// [AppTheme] in the app

/// {@endtemplate}
class ThemeScopeWidget extends StatefulWidget {
/// {@macro theme_scope_widget}
const ThemeScopeWidget({
super.key,
required this.child,
required this.preferences,
});

/// The child widget
final Widget child;

/// The shared preferences
final SharedPreferences preferences;

/// Initialize the [ThemeScopeWidget] with the given [child] widget
static Future<ThemeScopeWidget> initialize(Widget child) async {
final preferences = await SharedPreferences.getInstance();
return ThemeScopeWidget(
preferences: preferences,
child: child,
);
}

/// In order to use methods of [ThemeScopeWidget] this function
/// should be called first. Theme change process will handled by
/// [ThemeScopeWidget] automatically.
static ThemeScopeWidgetState? of(BuildContext context) {
return context.findRootAncestorStateOfType<ThemeScopeWidgetState>();
}

@override
State<ThemeScopeWidget> createState() => ThemeScopeWidgetState();
}

/// The state for [ThemeScopeWidget].
class ThemeScopeWidgetState extends State<ThemeScopeWidget> {
ThemeMode? _themeMode;

/// Change the theme mode
Future<void> changeTo(ThemeMode themeMode) async {
if (_themeMode == themeMode) return;

try {
final index = ThemeMode.values.indexOf(themeMode);
await widget.preferences.setInt(_kThemeMode, index);

setState(() {
_themeMode = themeMode;
});
} on Exception catch (_) {}
}

@override
void didChangeDependencies() {
super.didChangeDependencies();

try {
final themeModeIndex = widget.preferences.getInt(_kThemeMode) ?? 0;
final themeMode = ThemeMode.values[themeModeIndex];

_themeMode = themeMode;
} on Exception catch (_) {
_themeMode = ThemeMode.system;
}
}

@override
Widget build(BuildContext context) {
final brightness = MediaQuery.platformBrightnessOf(context);

final appTheme = switch (_themeMode!) {
ThemeMode.light => AppTheme.light(),
ThemeMode.dark => AppTheme.light(),
ThemeMode.system =>
brightness == Brightness.dark ? AppTheme.light() : AppTheme.light(),
};

return ThemeScope(
themeMode: _themeMode!,
appTheme: appTheme,
child: widget.child,
);
}
}
You can call ThemeScopeWidget.of(context).changeTo function to change your ThemeMode in anywhere!

The last step is to initialize your project with our Design System

We wrote this design system implementation as an independent package and used it in different apps.

Firstly, we should initialize our wrapper:

void main() async {
WidgetsFlutterBinding.ensureInitialized();
final app = await ThemeScopeWidget.initialize(const MyApp());
runApp(app);
}

We have another option to provide:

void main() async {
WidgetsFlutterBinding.ensureInitialized();
final preferences = await SharedPreferences.getInstance();

runApp(
ThemeScopeWidget(
preferences: preferences,
child: const MyApp(),
),
);
}

And, in the MaterialApp, it should be registered as an extension:

final theme = ThemeScope.of(context);

return MaterialApp(
title: 'Flutter App',
themeMode: theme.themeMode,
theme: ThemeData(extensions: [theme.appTheme]),
darkTheme: ThemeData(extensions: [theme.appTheme]),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);

There are a few extension methods that allow developers to access any theme, typography, and just a few lines of code.

context.buttonTheme.linkHover;
context.checkboxTheme.disabled;
context.typography.titleSmall;

To change the theme of app, just following function should be called:

final themeScope = ThemeScopeWidget.of(context);
themeScope.changeTo(ThemeMode.light);

What is about Assets?

In general, assets are the part of the design system. Therefore, we created an independent package that stores and emits PNGs, SVGs, etc, for any project as a package.

So, as you can see, the assets package stores fonts, raster, and vector images.

  • rasters mean PNGs, JPEGs, etc
  • vectors mean SVGs

Why did we divide them in the different package?

To optimize SVGs, we have used the new way, for all SVGs in the vectors package will be compiled to optimized version at the build time:

And, we need SSOT (Single Source of Truth) to get our assets:

/// {@template app_icons}
/// The [AppAssets] class contains all the icons used in the app.
/// {@endtemplate}
abstract class AppAssets {
/// person icon
static const person = AssetBytesLoader(
'vectors/person.svg',
packageName: 'assets',
);

/// left arrow icon
static const leftArrow = AssetBytesLoader(
'vectors/left_icon.svg',
packageName: 'assets',
);

static const sittingSad = 'rasters/sitting_sad.png';
static const magnifyingGlass = 'rasters/magnifying_glass.png';
}
You can divide raster and vector graphics class too

Keep in mind that, when you use raster graphic in any project as package (you defined asset package in pubspec and use it), you should define package field:

Image.asset(AppAssets.sittingSad, package: 'assets')

And, if you want to export font from other package you should define your fonts inside lib folder. For more information, you can check the Export fonts from a package article from Flutter documentation.

Testing your design system as an independent app

For this purpose, we are using Widgetbook. It is a huge topic to write about. In order not to prolong the article further, I did not write about this topic in more detail. It has a great documentation, you can check it.

That is it. If you like my article, don’t forget to clap!


Design System from scratch in Flutter was originally published in Flutter Community on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read the whole story
alvinashcraft
2 hours ago
reply
West Grove, PA
Share this story
Delete

Tech leaders congratulate Donald Trump on 2024 election victory

1 Share
Donald Trump has secured victory in the 2024 presidential election, marking one of the most remarkable comebacks in modern political history. As the news of his win reverberated across the globe, the tech industry’s most influential figures extended their congratulations and expressed their readiness to collaborate with his administration. The re-election of Trump has once again drawn the spotlight on how the tech world and Washington will navigate their sometimes contentious, yet undeniably interdependent relationship. Sam Altman, the CEO of OpenAI, kept his message concise and hopeful. Taking to X, Altman wrote, “congrats to President Trump. I wish for his… [Continue Reading]
Read the whole story
alvinashcraft
3 hours ago
reply
West Grove, PA
Share this story
Delete

Introducing the last cohort of AWS Heroes this year – November 2024

1 Share

As we gear up ahead of AWS re:Invent 2024, we’re thrilled to announce our final cohort of new AWS Heroes! These individuals exemplify expertise and dedication to leveraging AWS technologies and sharing knowledge. Their contributions to the AWS community are greatly appreciated, and today we’re excited to celebrate them.

Ayyanar Jeyakrishnan – Bengaluru, India

Machine Learning Hero Ayyanar Jeyakrishnan is a Principal Engineer/Executive Director at Wells Fargo. He is a seasoned machine learning and cloud enthusiast with a strong focus on AWS technologies. Ayyanar’s expertise includes creating data platforms and architecting DevOps and MLOps solutions to streamline the deployment and management of machine learning models on AWS. He is passionate about sharing his knowledge and frequently speaks at industry events and community meetups on MLOps, generative AI, and machine learning applications.

Dzenana Dzevlan – Sarajevo, Bosnia and Herzegovina

Community Hero Dzenana Dzevlan is a co-founder and Technical Manager at allOps Solutions, an APN Partner company. She is an expert practice lecturer at International Burch University, and actively shares her knowledge in DevOps, generative AI, and other emerging technologies. Dzenana’s passion for technology extends beyond the classroom, as she actively mentors students in cloud and AI solutions. A dedicated advocate for diversity in tech, she champions women’s inclusion and empowerment in the industry. Through speaking engagements and mentorship programs, Dzenana inspires and equips the next generation of IT and cloud professionals.

Kenneth Attard – Valletta, Malta

Community Hero Kenneth Attard is an Enterprise Architect at Betsson Group in Malta with over 20 years of technical experience, including the past eight years specializing in AWS Cloud networking, security, and governance. As the leader of the AWS Malta User Group and organizer of the Malta AWS Community Day, Kenneth is passionate about fostering knowledge and learning among cloud enthusiasts and professionals. He is also a frequent speaker at both local and international events, including AWS Cloud Days, AWS Summits, and AWS Community Days across multiple countries.

Marcin Sodkiewicz – Wrocław, Poland

Serverless Hero Marcin Sodkiewicz is a Principal Software Engineer at Ryanair. He has been there since 2016 and was part of a giant technological leap, from the data center into the cloud and teams building with a serverless-first mindset. Over those years, he has seen and learned a lot—especially the difference the cloud makes in quickly delivering high-quality, scalable, reliable, and profitable software. The profitability aspect is super important to him, as he works at a low-cost airline with a mission of “making travel affordable for everyone,” which matches his interest in building cost-optimized solutions that bring competitive advantages. Marcin blogs and talks about AWS through the lens of his favorite topics: event-driven and serverless architectures, resiliency, cost optimization, and observability. Additionally, he is one of the organizers of the AWS User Group in his city – Wrocław.

Stephen Sennett – Melbourne, Australia

Community Hero Stephen Sennett is a Senior Consultant at Kinetic IT in Australia. As an experienced cloud technologist, he has worked with AWS for over a decade as an architect, consultant, engineer, and educator. Stephen was a member of the AWS Community Builders program between 2021 and 2024, acting as a mentor to others in the AWS community, and serving as a public speaking coach for upcoming thought leaders through the AWS New Voices program. He is an experienced keynote speaker, delivering sessions at AWS Community Days, AWS Summits, and other tech conferences around the world. Outside of his professional role, Stephen is an active volunteer emergency management officer, and non-profit board director.

Vadym Kazulkin – Bonn, Germany

Serverless Hero Vadym Kazulkin is Head of Development at ip.labs GmbH (a Fujifilm subsidiary), and brings over two decades of Java ecosystem expertise. His current focus is designing and implementing highly scalable AWS Cloud applications, with a passion for serverless architecture. As co-organizer of the Java User Group Bonn meetup, Vadym actively shares his knowledge at local and international events, including AWS and Java meetups, conferences, AWS Community Days, and ServerlessDays. He values community engagement, both for sharing insights and continuous learning in cloud and serverless technologies.

Learn More

Visit the AWS Heroes website if you’d like to learn more about the AWS Heroes program, or to connect with a Hero near you.

Taylor

Read the whole story
alvinashcraft
3 hours ago
reply
West Grove, PA
Share this story
Delete

New AI experiences for Paint and Notepad begin rolling out to Windows Insiders

1 Share
Hello Windows Insiders, today we are beginning to roll out updates to Paint and Notepad to Windows Insiders in the Canary and Dev Channels on Windows 11.

Paint (version 11.2410.28.0)

Generative fill

In this update, we are introducing generative fill, a powerful new creation tool designed to help you make your artistic process more fun and intuitive. With generative fill, you can make edits and additions with just a few words while maintaining the existing art style of your project. Whether you’re a seasoned artist looking to add intricate details or a hobbyist experimenting with new ideas, generative fill helps you fine-tune your digital art, with just enough AI to assist you in realizing your creative vision while remaining in full control of the output. [caption id="attachment_177403" align="alignnone" width="900"]Paint app open with generative fill dialog open. Paint app open with generative fill dialog open.[/caption] To get started, use the Selection tool in Paint toolbar to make a Rectangle or Free-form selection. Upon selecting the area, you will see a small menu pop up anchored to your selection. Select the Generative fill option on the menu, use the text box to describe what you want to add to your selection, and hit Create. If you don’t like what was generated, simply press the Try again button. You can also try refining your selection or text prompt. Use the arrow buttons to cycle through the generated options, and once you are satisfied with one of the generated images, press the Keep button to apply it to your Paint canvas. You can learn about generative fill in Paint here. Generative fill will initially be available on Snapdragon-powered Copilot+ PCs. To use generative fill, you will need to sign in with your Microsoft account. This feature is only available for Windows Insiders in supported markets, where availability may vary based on regional criteria.  

Generative erase

We are also introducing generative erase, a new AI-powered tool that helps you remove unwanted objects from the canvas, filling in the empty space left behind to make it look like the object was never there. [caption id="attachment_177404" align="alignnone" width="901"]Paint app open with generative erase active. Paint app open with generative erase active.[/caption] To get started, select Generative erase on the left side of the canvas while using the eraser tool. With the generative erase brush, you can manually brush over one or multiple areas of the canvas to select the content you want to remove. “Add area to erase” lets you select more and “Reduce area to erase” lets you reduce your selection. After you are satisfied with your selection, click Apply to remove the object! You can also use rectangular or free-from selection tools to specify an area that you want to remove with the Generative erase command in the small menu pop up anchored to your selection or the right-click menu. Generative erase is available to users on all Windows 11 PCs.

Update to Cocreator

With this update, we have improved the underlying diffusion-based model for Cocreator to deliver better results faster, and with built-in moderation, it’s a creative experience you can trust. Cocreator is available on Snapdragon powered Copilot+ PCs. You can learn more about Paint Cocreator here.

Update to Image Creator

We launched Image Creator in Paint last year in preview, and we hope you have been enjoying the creative possibilities of AI image creation. We are excited to be expanding the preview to additional markets. Image Creator in Paint is available in preview to all Windows 11 users in the following regions: United States, France, UK, Canada, Italy, and Germany. To use Image Creator, you need to sign in with your Microsoft account. Microsoft 365 Personal and Family and Copilot Pro subscribers in Australia, New Zealand, Malaysia, Singapore, Taiwan, and Thailand will also now be able to use AI credits to use Image Creator in Paint. Learn more about AI credits. You can learn more about Image Creator in Paint here. FEEDBACK: Please file feedback in Feedback Hub (WIN + F) under Apps > Paint.

Notepad (version 11.2410.15.0)

With this update, we are introducing the ability to rewrite content in Notepad with the help of generative AI. You can rephrase sentences, adjust the tone, and modify the length of your content based on your preferences to refine your text. [caption id="attachment_177405" align="alignnone" width="1024"]Notepad app with rewrite dialog open. Notepad app with rewrite dialog open.[/caption] To get started, select the text you want to rewrite, then right-click and choose the Rewrite option, select Rewrite from the menu bar, or use the Ctrl + I keyboard shortcut. Notepad will generate three variations of the rewritten text for you to choose from. Select one, or if you want to further refine the output, you can customize rewrite settings and click Retry to generate additional versions. Options to make your content longer or shorter and modifying the tone or format let you easily adjust your content for specific goals. The previous versions are still preserved in the current dialog, so you can easily revert to earlier versions if needed. If you prefer, you can disable the rewrite feature in app settings. Rewrite in Notepad is available in preview to all users on Windows 11 in the following regions: United States, France, UK, Canada, Italy, and Germany. To use rewrite, you need to sign in with your Microsoft account. Microsoft 365 Personal and Family and Copilot Pro subscribers in Australia, New Zealand, Malaysia, Singapore, Taiwan, and Thailand will also be able to use AI credits to use rewrite in Notepad. Learn more about AI credits. You can learn more about Rewrite in Notepad here. We have also continued to improve Notepad launch performance. With this update, most users will see app launch time improve by more than 35%, with some users seeing improvements of 55% or more. FEEDBACK: Please share your feedback in Feedback Hub (WIN + F) under Apps > Notepad. [These features in Paint and Notepad are beginning to rollout in these updates, so it may not be available to all Insiders in the Canary and Dev Channels just yet as we plan to monitor feedback and see how it lands before pushing them out to everyone.] As always, we love getting feedback from the community and we will be looking out for your thoughts and suggestions on these updates! Thanks, Dave Grochocki, Principal Group Product Manager – Windows Inbox Apps
Read the whole story
alvinashcraft
3 hours ago
reply
West Grove, PA
Share this story
Delete
Next Page of Stories