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/text/article_header.dart b/lib/app/features/feed/widgets/text/article_header.dart new file mode 100644 index 000000000..aa63ab53a --- /dev/null +++ b/lib/app/features/feed/widgets/text/article_header.dart @@ -0,0 +1,30 @@ +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/core/screen_side_offset.dart'; + +class ArticleHeader extends StatelessWidget { + const ArticleHeader({super.key, required this.headerText, this.main}); + + final String headerText; + final bool? main; + + TextStyle getStyle(BuildContext context) { + if (main == true) { + return context.theme.appTextThemes.headline2 + .copyWith(color: context.theme.appColors.feedText); + } + return context.theme.appTextThemes.title + .copyWith(color: context.theme.appColors.feedText); + } + + @override + Widget build(BuildContext context) { + return ScreenSideOffset( + child: Text( + headerText, + style: getStyle(context), + ), + ); + } +} diff --git a/lib/app/features/feed/widgets/text/widgetbook.dart b/lib/app/features/feed/widgets/text/widgetbook.dart new file mode 100644 index 000000000..1df6d7349 --- /dev/null +++ b/lib/app/features/feed/widgets/text/widgetbook.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:ice/app/features/feed/widgets/text/article_header.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +@widgetbook.UseCase( + name: 'article header', + type: ArticleHeader, +) +Widget plainArticleHeader(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ArticleHeader( + headerText: + 'Coinbase Launches USDC Yields for Global Customers Amid US Crackdown', + main: true, + ), + ArticleHeader( + headerText: + 'In 22 years, AI will perform 50% of work tasks, These are the results of McKinsey study', + ), + ], + ); +} diff --git a/lib/app/shared/widgets/core/screen_side_offset.dart b/lib/app/shared/widgets/core/screen_side_offset.dart new file mode 100644 index 000000000..238c9b29a --- /dev/null +++ b/lib/app/shared/widgets/core/screen_side_offset.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ice/app/features/core/providers/template_provider.dart'; + +class ScreenSideOffset extends StatelessWidget { + const ScreenSideOffset({super.key, required this.child}); + + final Widget child; + + static double get offset => + ScreenUtil().setWidth(appTemplate.screenSideOffset); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: offset, + ), + child: child, + ); + } +} diff --git a/lib/app/shared/widgets/images/post_image/post_image.dart b/lib/app/shared/widgets/images/post_image/post_image.dart new file mode 100644 index 000000000..b7bdf497b --- /dev/null +++ b/lib/app/shared/widgets/images/post_image/post_image.dart @@ -0,0 +1,88 @@ +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/shared/widgets/core/screen_side_offset.dart'; +import 'package:ice/app/shared/widgets/tiles/read_time/read_time_tile.dart'; // Make sure this is correctly imported for your project + +const double borderRadius = 12.0; + +class PostImage extends StatelessWidget { + const PostImage({ + super.key, + required this.imageUrl, + this.minutesToRead, + this.minutesToReadAlignment, + }); + + final String imageUrl; + final int? minutesToRead; + final Alignment? minutesToReadAlignment; + + String getAdaptiveImageUrl(double imageWidth) { + return '$imageUrl?width=${imageWidth.toInt()}'; + } + + BorderRadius getOverlayBorderRadius(Alignment alignment) { + if (alignment == Alignment.bottomRight || alignment == Alignment.topLeft) { + return const BorderRadius.only( + topLeft: Radius.circular(borderRadius), + bottomRight: Radius.circular(borderRadius), + ); + } + if (alignment == Alignment.topRight || alignment == Alignment.bottomLeft) { + return const BorderRadius.only( + topRight: Radius.circular(borderRadius), + bottomLeft: Radius.circular(borderRadius), + ); + } + return const BorderRadius.all(Radius.circular(borderRadius)); + } + + @override + Widget build(BuildContext context) { + final double paddingHorizontal = ScreenSideOffset.offset; + 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: getAdaptiveImageUrl(imageWidth), + width: imageWidth, + fit: BoxFit.cover, // Cover the widget's bounds + ), + if (minutesToRead != null) ...[ + Container( + // Adjust the overlay margin + padding: + EdgeInsets.symmetric(horizontal: 8.0.w, vertical: 4.0.w), + // Adjust the overlay padding + decoration: BoxDecoration( + color: context.theme.appColors.tertararyBackground, + // Background color + border: Border.all( + color: context.theme.appColors.onTerararyFill, + ), + // Border color + borderRadius: getOverlayBorderRadius(alignment), + ), + child: ReadTimeTile( + minutesToRead: minutesToRead!, + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/app/shared/widgets/images/post_image/widgetbook.dart b/lib/app/shared/widgets/images/post_image/widgetbook.dart new file mode 100644 index 000000000..7e35663e3 --- /dev/null +++ b/lib/app/shared/widgets/images/post_image/widgetbook.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:ice/app/shared/widgets/images/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/shared/widgets/tiles/read_time/read_time_tile.dart b/lib/app/shared/widgets/tiles/read_time/read_time_tile.dart new file mode 100644 index 000000000..e6cef7cf1 --- /dev/null +++ b/lib/app/shared/widgets/tiles/read_time/read_time_tile.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'; + +const double iconSize = 16.0; + +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.feedText, + ), + const SizedBox(width: 3.0), + // Space between icon and text + Text( + '$minutesToRead min read', + style: context.theme.appTextThemes.caption + .copyWith(color: context.theme.appColors.feedText), + ), + // Text displaying minutes + ], + ); + } +} diff --git a/lib/app/shared/widgets/tiles/read_time/widgetbook.dart b/lib/app/shared/widgets/tiles/read_time/widgetbook.dart new file mode 100644 index 000000000..9352a49b9 --- /dev/null +++ b/lib/app/shared/widgets/tiles/read_time/widgetbook.dart @@ -0,0 +1,24 @@ +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/tiles/read_time/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, + // Set the background color to white + child: ReadTimeTile( + minutesToRead: context.knobs.int.slider( + label: 'Minutes To Read', + initialValue: 7, + max: 120, + ), + ), + ); +} diff --git a/lib/app/templates/basic.json b/lib/app/templates/basic.json index a03c21c51..784fb3846 100644 --- a/lib/app/templates/basic.json +++ b/lib/app/templates/basic.json @@ -5,6 +5,7 @@ "primaryText": "#0E0E0E", "secondaryText": "#494949", "tertararyText": "#9A9A9A", + "feedText": "#333436", "primaryBackground": "#F5F7FF", "secondaryBackground": "#FFFFFF", "tertararyBackground": "#FAFBFF", @@ -23,6 +24,7 @@ "primaryText": "#0E0E0E", "secondaryText": "#494949", "tertararyText": "#9A9A9A", + "feedText": "#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 @@ -79,5 +117,6 @@ "icon": { "width": 24, "height": 24 - } + }, + "screenSideOffset": 16.0 } diff --git a/lib/app/templates/template.dart b/lib/app/templates/template.dart index 505b8de1f..005837928 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.feedText, this.primaryBackground, this.secondaryBackground, this.tertararyBackground, @@ -38,6 +39,8 @@ class TemplateColors { @ColorConverter() Color tertararyText; @ColorConverter() + Color feedText; + @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; @@ -228,6 +233,7 @@ class Template { this.menuButton, this.iconButton, this.icon, + this.screenSideOffset, ); factory Template.fromJson(Map json) => @@ -241,4 +247,5 @@ class Template { TemplateMenuButtonTheme menuButton; TemplateIconButtonTheme iconButton; TemplateIconTheme icon; + double screenSideOffset; } diff --git a/lib/app/theme/app_colors.dart b/lib/app/theme/app_colors.dart index ebc65ee2d..96df58bec 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.feedText, 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, + feedText: templateColors.feedText, 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 feedText; final Color primaryBackground; final Color secondaryBackground; final Color tertararyBackground; @@ -65,6 +68,7 @@ class AppColorsExtension extends ThemeExtension { Color? primaryText, Color? secondaryText, Color? tertararyText, + Color? feedText, 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, + feedText: feedText ?? this.feedText, 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)!, + feedText: Color.lerp(feedText, other.feedText, 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/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/widgetbook.dart b/lib/widgetbook.dart new file mode 100644 index 000000000..e66449137 --- /dev/null +++ b/lib/widgetbook.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:ice/app/features/core/providers/init_provider.dart'; +import 'package:ice/app/features/core/providers/template_provider.dart'; +import 'package:ice/app/features/core/providers/theme_mode_provider.dart'; +import 'package:ice/app/templates/template.dart'; +import 'package:ice/app/theme/theme.dart'; +import 'package:ice/generated/app_localizations.dart'; +import 'package:ice/widgetbook.directories.g.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +void main() { + runApp(const ProviderScope(child: WidgetbookApp())); +} + +@widgetbook.App() +class WidgetbookApp extends ConsumerWidget { + const WidgetbookApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final AsyncValue init = ref.watch(initAppProvider); + + return init.when( + data: (void data) => Widgetbook.material( + directories: directories, + addons: [AlignmentAddon( + ),DeviceFrameAddon( + devices: Devices.all, + ),LocalizationAddon( + locales: [ + const Locale('en'), + ], + localizationsDelegates: >[ + DefaultWidgetsLocalizations.delegate, + ], + ),], + appBuilder: (BuildContext context, Widget child) { + final ThemeMode appThemeMode = ref.watch(appThemeModeProvider); + final AsyncValue