diff --git a/android/gradle.properties b/android/gradle.properties index 94adc3a3f..eea8bfb4e 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8192m +org.gradle.parallel=true android.useAndroidX=true android.enableJetifier=true diff --git a/lib/app/features/core/providers/template_provider.dart b/lib/app/features/core/providers/template_provider.dart index ecba3094d..7c51ec016 100644 --- a/lib/app/features/core/providers/template_provider.dart +++ b/lib/app/features/core/providers/template_provider.dart @@ -6,11 +6,11 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'template_provider.g.dart'; -Template? _template; +late Template _template; //TODO::discuss this approach Template get appTemplate { - return _template!; + return _template; } @Riverpod(keepAlive: true) diff --git a/lib/app/features/feed/widgets/article_header/article_header.dart b/lib/app/features/feed/widgets/article_header/article_header.dart new file mode 100644 index 000000000..a76985818 --- /dev/null +++ b/lib/app/features/feed/widgets/article_header/article_header.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:ice/app/extensions/build_context.dart'; +import 'package:ice/app/extensions/theme_data.dart'; +import 'package:ice/app/shared/widgets/screen_side_offset/screen_side_offset.dart'; + +class ArticleHeader extends StatelessWidget { + const ArticleHeader({ + super.key, + required this.headerText, + this.isMainHeader = false, + }); + + final String headerText; + final bool isMainHeader; + + TextStyle getStyle(BuildContext context) { + final Color color = context.theme.appColors.sharkText; + if (isMainHeader) { + return context.theme.appTextThemes.headline2.copyWith(color: color); + } + return context.theme.appTextThemes.title.copyWith(color: color); + } + + @override + Widget build(BuildContext context) { + return ScreenSideOffset.small( + child: Text( + headerText, + style: getStyle(context), + ), + ); + } +} diff --git a/lib/app/features/feed/widgets/article_header/widgetbook.dart b/lib/app/features/feed/widgets/article_header/widgetbook.dart new file mode 100644 index 000000000..f2ec88dd3 --- /dev/null +++ b/lib/app/features/feed/widgets/article_header/widgetbook.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:ice/app/features/feed/widgets/article_header/article_header.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +@widgetbook.UseCase( + name: 'article header', + type: ArticleHeader, +) +Widget plainArticleHeader(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ArticleHeader( + headerText: context.knobs.string( + label: 'Article Header', + initialValue: + 'Coinbase Launches USDC Yields for Global Customers Amid US Crackdown', + ), + isMainHeader: true, + ), + ArticleHeader( + headerText: context.knobs.string( + label: 'Article Header', + initialValue: + 'In 22 years, AI will perform 50% of work tasks, These are the results of McKinsey study', + ), + ), + ], + ); +} diff --git a/lib/app/features/feed/widgets/post_image/post_image.dart b/lib/app/features/feed/widgets/post_image/post_image.dart new file mode 100644 index 000000000..d056010e2 --- /dev/null +++ b/lib/app/features/feed/widgets/post_image/post_image.dart @@ -0,0 +1,81 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ice/app/extensions/build_context.dart'; +import 'package:ice/app/extensions/theme_data.dart'; +import 'package:ice/app/features/feed/widgets/read_time_tile/read_time_tile.dart'; +import 'package:ice/app/shared/widgets/screen_side_offset/screen_side_offset.dart'; +import 'package:ice/utils/Image_utils.dart'; + +double borderRadius = 12.0.w; + +class PostImage extends StatelessWidget { + const PostImage({ + super.key, + required this.imageUrl, + this.minutesToRead, + this.minutesToReadAlignment, + }); + + final String imageUrl; + final int? minutesToRead; + final Alignment? minutesToReadAlignment; + + BorderRadius getOverlayBorderRadius(Alignment alignment) { + if (alignment == Alignment.bottomRight || alignment == Alignment.topLeft) { + return BorderRadius.only( + topLeft: Radius.circular(borderRadius), + bottomRight: Radius.circular(borderRadius), + ); + } + if (alignment == Alignment.topRight || alignment == Alignment.bottomLeft) { + return BorderRadius.only( + topRight: Radius.circular(borderRadius), + bottomLeft: Radius.circular(borderRadius), + ); + } + return BorderRadius.all(Radius.circular(borderRadius)); + } + + @override + Widget build(BuildContext context) { + final double paddingHorizontal = ScreenSideOffset.defaultSmallMargin; + final double imageWidth = + MediaQuery.of(context).size.width - paddingHorizontal * 2; + + final Alignment alignment = minutesToReadAlignment ?? Alignment.bottomRight; + + return Padding( + padding: EdgeInsets.symmetric(horizontal: paddingHorizontal), + child: ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: Stack( + alignment: alignment, + children: [ + CachedNetworkImage( + imageUrl: ImageUtils.getAdaptiveImageUrl(imageUrl, imageWidth), + width: imageWidth, + fit: BoxFit.cover, + ), + if (minutesToRead != null) ...[ + Container( + padding: + EdgeInsets.symmetric(horizontal: 8.0.w, vertical: 4.0.w), + decoration: BoxDecoration( + color: context.theme.appColors.tertararyBackground, + border: Border.all( + color: context.theme.appColors.onTerararyFill, + ), + borderRadius: getOverlayBorderRadius(alignment), + ), + child: ReadTimeTile( + minutesToRead: minutesToRead!, + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/app/features/feed/widgets/post_image/widgetbook.dart b/lib/app/features/feed/widgets/post_image/widgetbook.dart new file mode 100644 index 000000000..80097046a --- /dev/null +++ b/lib/app/features/feed/widgets/post_image/widgetbook.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:ice/app/features/feed/widgets/post_image/post_image.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +@widgetbook.UseCase( + name: 'base case', + type: PostImage, +) +Widget baseUseCase(BuildContext context) { + return const PostImage( + imageUrl: + 'https://ice.io/wp-content/uploads/2023/03/Pre-Release-600x403.png', + ); +} + +@widgetbook.UseCase( + name: 'with read time overlay', + type: PostImage, +) +Widget withReadTimeUseCase(BuildContext context) { + return const PostImage( + imageUrl: + 'https://ice.io/wp-content/uploads/2023/03/Pre-Release-600x403.png', + minutesToRead: 7, + minutesToReadAlignment: Alignment.topRight, + ); +} diff --git a/lib/app/features/feed/widgets/read_time_tile/read_time_tile.dart b/lib/app/features/feed/widgets/read_time_tile/read_time_tile.dart new file mode 100644 index 000000000..5d4682149 --- /dev/null +++ b/lib/app/features/feed/widgets/read_time_tile/read_time_tile.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ice/app/extensions/build_context.dart'; +import 'package:ice/app/extensions/theme_data.dart'; + +double iconSize = 16.0.w; + +class ReadTimeTile extends StatelessWidget { + const ReadTimeTile({super.key, required this.minutesToRead}); + + final int minutesToRead; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.access_time, + size: iconSize, + color: context.theme.appColors.sharkText, + ), + SizedBox(width: 3.0.w), + Text( + context.i18n.read_time_in_mins(minutesToRead), + style: context.theme.appTextThemes.caption + .copyWith(color: context.theme.appColors.sharkText), + ), + ], + ); + } +} diff --git a/lib/app/features/feed/widgets/read_time_tile/widgetbook.dart b/lib/app/features/feed/widgets/read_time_tile/widgetbook.dart new file mode 100644 index 000000000..09044e2ff --- /dev/null +++ b/lib/app/features/feed/widgets/read_time_tile/widgetbook.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:ice/app/extensions/build_context.dart'; +import 'package:ice/app/extensions/theme_data.dart'; +import 'package:ice/app/features/feed/widgets/read_time_tile/read_time_tile.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +@widgetbook.UseCase( + name: 'base case', + type: ReadTimeTile, +) +Widget baseUseCase(BuildContext context) { + return ColoredBox( + color: context.theme.appColors.onTerararyFill, + child: ReadTimeTile( + minutesToRead: context.knobs.int.slider( + label: 'Minutes To Read', + initialValue: 7, + max: 120, + ), + ), + ); +} diff --git a/lib/app/shared/widgets/screen_side_offset/screen_side_offset.dart b/lib/app/shared/widgets/screen_side_offset/screen_side_offset.dart index 09f7d28a3..c4186826a 100644 --- a/lib/app/shared/widgets/screen_side_offset/screen_side_offset.dart +++ b/lib/app/shared/widgets/screen_side_offset/screen_side_offset.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -double defaultLargeMargin = 44.0.w; -double defaultSmallMargin = 16.0.w; - class ScreenSideOffset extends StatelessWidget { const ScreenSideOffset._({ super.key, @@ -38,6 +35,11 @@ class ScreenSideOffset extends StatelessWidget { child: child, ); } + + static double get defaultSmallMargin => 16.0.w; + + static double get defaultLargeMargin => 44.0.w; + final Widget child; final EdgeInsets insets; diff --git a/lib/app/shared/widgets/socials/socials.dart b/lib/app/shared/widgets/socials/socials.dart index 5d5dd6d44..a7bacff3f 100644 --- a/lib/app/shared/widgets/socials/socials.dart +++ b/lib/app/shared/widgets/socials/socials.dart @@ -53,8 +53,10 @@ class _SocialsState extends State { final double buttonWidth = context.theme.iconButtonTheme.style?.iconSize ?.resolve({}) ?? defaultSocialIconButtonSide; - final double spaceBetweenButtons = - (screenWidth - 2 * defaultLargeMargin - 4 * buttonWidth) / 3; + final double spaceBetweenButtons = (screenWidth - + 2 * ScreenSideOffset.defaultLargeMargin - + 4 * buttonWidth) / + 3; return Column( children: [ diff --git a/lib/app/templates/basic.json b/lib/app/templates/basic.json index a03c21c51..112c20a5e 100644 --- a/lib/app/templates/basic.json +++ b/lib/app/templates/basic.json @@ -5,6 +5,7 @@ "primaryText": "#0E0E0E", "secondaryText": "#494949", "tertararyText": "#9A9A9A", + "sharkText": "#333436", "primaryBackground": "#F5F7FF", "secondaryBackground": "#FFFFFF", "tertararyBackground": "#FAFBFF", @@ -23,6 +24,7 @@ "primaryText": "#0E0E0E", "secondaryText": "#494949", "tertararyText": "#9A9A9A", + "sharkText": "#333436", "primaryBackground": "#F5F7FF", "secondaryBackground": "#FFFFFF", "tertararyBackground": "#FAFBFF", @@ -38,16 +40,52 @@ } }, "textThemes": { - "headline1": { "fontSize": 28, "fontWeight": 700 }, - "inputFieldText": { "fontSize": 25, "fontWeight": 700 }, - "title": { "fontSize": 17, "fontWeight": 500 }, - "subtitle": { "fontSize": 15, "fontWeight": 600 }, - "subtitle2": { "fontSize": 15, "fontWeight": 500 }, - "body": { "fontSize": 13, "height": 1.38, "fontWeight": 600 }, - "body2": { "fontSize": 13, "fontWeight": 400 }, - "caption": { "fontSize": 12, "fontWeight": 500 }, - "caption2": { "fontSize": 12, "fontWeight": 400 }, - "caption3": { "fontSize": 11, "height": 1.63, "fontWeight": 400 } + "headline1": { + "fontSize": 28, + "fontWeight": 700 + }, + "headline2": { + "fontSize": 24, + "fontWeight": 700 + }, + "inputFieldText": { + "fontSize": 25, + "fontWeight": 700 + }, + "title": { + "fontSize": 17, + "fontWeight": 600 + }, + "subtitle": { + "fontSize": 15, + "fontWeight": 600 + }, + "subtitle2": { + "fontSize": 15, + "fontWeight": 500 + }, + "body": { + "fontSize": 13, + "height": 1.38, + "fontWeight": 600 + }, + "body2": { + "fontSize": 13, + "fontWeight": 400 + }, + "caption": { + "fontSize": 12, + "fontWeight": 500 + }, + "caption2": { + "fontSize": 12, + "fontWeight": 400 + }, + "caption3": { + "fontSize": 11, + "height": 1.63, + "fontWeight": 400 + } }, "appBar": { "toolbarHeight": 50 diff --git a/lib/app/templates/template.dart b/lib/app/templates/template.dart index 505b8de1f..59499bf8e 100644 --- a/lib/app/templates/template.dart +++ b/lib/app/templates/template.dart @@ -12,6 +12,7 @@ class TemplateColors { this.primaryText, this.secondaryText, this.tertararyText, + this.sharkText, this.primaryBackground, this.secondaryBackground, this.tertararyBackground, @@ -38,6 +39,8 @@ class TemplateColors { @ColorConverter() Color tertararyText; @ColorConverter() + Color sharkText; + @ColorConverter() Color primaryBackground; @ColorConverter() Color secondaryBackground; @@ -101,6 +104,7 @@ class TemplateTextTheme { class TemplateTextThemes { TemplateTextThemes( this.headline1, + this.headline2, this.inputFieldText, this.title, this.subtitle, @@ -116,6 +120,7 @@ class TemplateTextThemes { _$TemplateTextThemesFromJson(json); TemplateTextTheme headline1; + TemplateTextTheme headline2; TemplateTextTheme inputFieldText; TemplateTextTheme title; TemplateTextTheme subtitle; diff --git a/lib/app/theme/app_colors.dart b/lib/app/theme/app_colors.dart index ebc65ee2d..680b56692 100644 --- a/lib/app/theme/app_colors.dart +++ b/lib/app/theme/app_colors.dart @@ -7,6 +7,7 @@ class AppColorsExtension extends ThemeExtension { required this.primaryText, required this.secondaryText, required this.tertararyText, + required this.sharkText, required this.primaryBackground, required this.secondaryBackground, required this.tertararyBackground, @@ -27,6 +28,7 @@ class AppColorsExtension extends ThemeExtension { primaryText: templateColors.primaryText, secondaryText: templateColors.secondaryText, tertararyText: templateColors.tertararyText, + sharkText: templateColors.sharkText, primaryBackground: templateColors.primaryBackground, secondaryBackground: templateColors.secondaryBackground, tertararyBackground: templateColors.tertararyBackground, @@ -46,6 +48,7 @@ class AppColorsExtension extends ThemeExtension { final Color primaryText; final Color secondaryText; final Color tertararyText; + final Color sharkText; final Color primaryBackground; final Color secondaryBackground; final Color tertararyBackground; @@ -65,6 +68,7 @@ class AppColorsExtension extends ThemeExtension { Color? primaryText, Color? secondaryText, Color? tertararyText, + Color? sharkText, Color? primaryBackground, Color? secondaryBackground, Color? tertararyBackground, @@ -83,6 +87,7 @@ class AppColorsExtension extends ThemeExtension { primaryText: primaryText ?? this.primaryText, secondaryText: secondaryText ?? this.secondaryText, tertararyText: tertararyText ?? this.tertararyText, + sharkText: sharkText ?? this.sharkText, primaryBackground: primaryBackground ?? this.primaryBackground, secondaryBackground: secondaryBackground ?? this.secondaryBackground, tertararyBackground: tertararyBackground ?? this.tertararyBackground, @@ -114,6 +119,7 @@ class AppColorsExtension extends ThemeExtension { primaryText: Color.lerp(primaryText, other.primaryText, t)!, secondaryText: Color.lerp(secondaryText, other.secondaryText, t)!, tertararyText: Color.lerp(tertararyText, other.tertararyText, t)!, + sharkText: Color.lerp(sharkText, other.sharkText, t)!, primaryBackground: Color.lerp(primaryBackground, other.primaryBackground, t)!, secondaryBackground: diff --git a/lib/app/theme/app_text_themes.dart b/lib/app/theme/app_text_themes.dart index c7a6c4ae9..ec3f51a07 100644 --- a/lib/app/theme/app_text_themes.dart +++ b/lib/app/theme/app_text_themes.dart @@ -4,6 +4,7 @@ import 'package:ice/app/templates/template.dart'; class AppTextThemesExtension extends ThemeExtension { const AppTextThemesExtension({ required this.headline1, + required this.headline2, required this.inputFieldText, required this.title, required this.subtitle, @@ -18,6 +19,7 @@ class AppTextThemesExtension extends ThemeExtension { factory AppTextThemesExtension.fromTemplate(TemplateTextThemes textThemes) { return AppTextThemesExtension( headline1: TemplateTextStyle.fromTemplate(textThemes.headline1), + headline2: TemplateTextStyle.fromTemplate(textThemes.headline2), inputFieldText: TemplateTextStyle.fromTemplate(textThemes.inputFieldText), title: TemplateTextStyle.fromTemplate(textThemes.title), subtitle: TemplateTextStyle.fromTemplate(textThemes.subtitle), @@ -31,6 +33,7 @@ class AppTextThemesExtension extends ThemeExtension { } final TextStyle headline1; + final TextStyle headline2; final TextStyle inputFieldText; final TextStyle title; final TextStyle subtitle; @@ -44,6 +47,7 @@ class AppTextThemesExtension extends ThemeExtension { @override ThemeExtension copyWith({ TextStyle? headline1, + TextStyle? headline2, TextStyle? inputFieldText, TextStyle? title, TextStyle? subtitle, @@ -56,6 +60,7 @@ class AppTextThemesExtension extends ThemeExtension { }) { return AppTextThemesExtension( headline1: headline1 ?? this.headline1, + headline2: headline2 ?? this.headline2, inputFieldText: inputFieldText ?? this.inputFieldText, title: title ?? this.title, subtitle: subtitle ?? this.subtitle, @@ -79,6 +84,7 @@ class AppTextThemesExtension extends ThemeExtension { return AppTextThemesExtension( headline1: TextStyle.lerp(headline1, other.headline1, t)!, + headline2: TextStyle.lerp(headline2, other.headline2, t)!, inputFieldText: TextStyle.lerp(inputFieldText, other.inputFieldText, t)!, title: TextStyle.lerp(title, other.title, t)!, subtitle: TextStyle.lerp(subtitle, other.subtitle, t)!, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6124619ea..13a8253c0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -56,5 +56,6 @@ "secured_by_ice": "ice", "wallet_header_ice_wallet": "ice.wallet", "email_input_placeholder": "Email address", - "email_input_invalid_email_format": "Invalid email format" + "email_input_invalid_email_format": "Invalid email format", + "read_time_in_mins": "{mins} min read" } diff --git a/lib/main.dart b/lib/main.dart index d1953b3da..521cd3f20 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,13 +29,17 @@ class IceApp extends HookConsumerWidget { return ScreenUtilInit( designSize: const Size(375, 812), - builder: (BuildContext context, Widget? widget) => MaterialApp.router( - localizationsDelegates: I18n.localizationsDelegates, - supportedLocales: I18n.supportedLocales, - theme: template.whenOrNull(data: buildLightTheme), - darkTheme: template.whenOrNull(data: buildDarkTheme), - themeMode: appThemeMode, - routerConfig: appRouter, + builder: (BuildContext context, Widget? widget) => MediaQuery( + /// Setting font does not change with system font size + data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), + child: MaterialApp.router( + localizationsDelegates: I18n.localizationsDelegates, + supportedLocales: I18n.supportedLocales, + theme: template.whenOrNull(data: buildLightTheme), + darkTheme: template.whenOrNull(data: buildDarkTheme), + themeMode: appThemeMode, + routerConfig: appRouter, + ), ), ); } diff --git a/lib/utils/image_utils.dart b/lib/utils/image_utils.dart new file mode 100644 index 000000000..d2c4ba3eb --- /dev/null +++ b/lib/utils/image_utils.dart @@ -0,0 +1,5 @@ +mixin ImageUtils { + static String getAdaptiveImageUrl(String imageUrl, double imageWidth) { + return '$imageUrl?width=${imageWidth.toInt()}'; + } +} diff --git a/lib/widgetbook/main.dart b/lib/widgetbook/main.dart index 290a61fb8..d9928839d 100644 --- a/lib/widgetbook/main.dart +++ b/lib/widgetbook/main.dart @@ -26,6 +26,12 @@ class WidgetbookApp extends ConsumerWidget { return init.when( data: (void data) => Widgetbook.material( directories: directories, + addons: [ + AlignmentAddon(), + DeviceFrameAddon( + devices: Devices.all, + ), + ], appBuilder: (BuildContext context, Widget child) { final ThemeMode appThemeMode = ref.watch(appThemeModeProvider); final AsyncValue