💄 Optimize news design
This commit is contained in:
		@@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'package:material_symbols_icons/symbols.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
import 'package:styled_widget/styled_widget.dart';
 | 
			
		||||
import 'package:surface/providers/config.dart';
 | 
			
		||||
import 'package:surface/providers/post.dart';
 | 
			
		||||
import 'package:surface/providers/sn_network.dart';
 | 
			
		||||
import 'package:surface/screens/post/post_detail.dart';
 | 
			
		||||
@@ -96,6 +97,8 @@ class _ExploreScreenState extends State<ExploreScreen> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final cfg = context.read<ConfigProvider>();
 | 
			
		||||
 | 
			
		||||
    return AppScaffold(
 | 
			
		||||
      floatingActionButtonLocation: ExpandableFab.location,
 | 
			
		||||
      floatingActionButton: ExpandableFab(
 | 
			
		||||
@@ -243,8 +246,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
 | 
			
		||||
                    ),
 | 
			
		||||
                    openColor: Colors.transparent,
 | 
			
		||||
                    openElevation: 0,
 | 
			
		||||
                    closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75),
 | 
			
		||||
                    transitionType: ContainerTransitionType.fade,
 | 
			
		||||
                    closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
 | 
			
		||||
                          cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
 | 
			
		||||
                        ),
 | 
			
		||||
                    closedShape: const RoundedRectangleBorder(
 | 
			
		||||
                      borderRadius: BorderRadius.all(Radius.circular(16)),
 | 
			
		||||
                    ),
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ class HomeScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _HomeScreenState extends State<HomeScreen> {
 | 
			
		||||
  static const List<HomeScreenDashEntry> kCards = [
 | 
			
		||||
  late final List<HomeScreenDashEntry> kCards = [
 | 
			
		||||
    HomeScreenDashEntry(
 | 
			
		||||
      name: 'dashEntryRecommendation',
 | 
			
		||||
      child: _HomeDashRecommendationPostWidget(),
 | 
			
		||||
@@ -69,7 +69,7 @@ class _HomeScreenState extends State<HomeScreen> {
 | 
			
		||||
    HomeScreenDashEntry(
 | 
			
		||||
      name: 'dashEntryTodayNews',
 | 
			
		||||
      child: _HomeDashTodayNews(),
 | 
			
		||||
      cols: 2,
 | 
			
		||||
      cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2,
 | 
			
		||||
    ),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
@@ -293,7 +293,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
 | 
			
		||||
                    Text(
 | 
			
		||||
                      _article!.title,
 | 
			
		||||
                      style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18),
 | 
			
		||||
                      maxLines: 2,
 | 
			
		||||
                      maxLines: MediaQuery.of(context).size.width >= 640 ? 2 : 1,
 | 
			
		||||
                      overflow: TextOverflow.ellipsis,
 | 
			
		||||
                    ),
 | 
			
		||||
                    Text(
 | 
			
		||||
 
 | 
			
		||||
@@ -175,54 +175,57 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
 | 
			
		||||
          ),
 | 
			
		||||
          if (_articleFragment != null && _isReadingFromReader)
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: SingleChildScrollView(
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                  spacing: 8,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
 | 
			
		||||
                    Builder(builder: (context) {
 | 
			
		||||
                      final htmlDescription = parse(_article!.description);
 | 
			
		||||
                      return Text(
 | 
			
		||||
                        htmlDescription.children.map((ele) => ele.text.trim()).join(),
 | 
			
		||||
                        style: Theme.of(context).textTheme.bodyMedium,
 | 
			
		||||
                      );
 | 
			
		||||
                    }),
 | 
			
		||||
                    Builder(builder: (context) {
 | 
			
		||||
                      final date = _article!.publishedAt ?? _article!.createdAt;
 | 
			
		||||
                      return Row(
 | 
			
		||||
                        spacing: 2,
 | 
			
		||||
                        children: [
 | 
			
		||||
                          Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                          Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
 | 
			
		||||
                          Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ).opacity(0.75);
 | 
			
		||||
                    }),
 | 
			
		||||
                    Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
 | 
			
		||||
                    const Divider(),
 | 
			
		||||
                    ..._parseHtmlToWidgets(_articleFragment!.children),
 | 
			
		||||
                    const Divider(),
 | 
			
		||||
                    InkWell(
 | 
			
		||||
                      child: Row(
 | 
			
		||||
                        mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                        children: [
 | 
			
		||||
                          Text(
 | 
			
		||||
                            'Reference from original website',
 | 
			
		||||
                            style: TextStyle(decoration: TextDecoration.underline),
 | 
			
		||||
                          ),
 | 
			
		||||
                          const Gap(4),
 | 
			
		||||
                          Icon(Icons.launch, size: 16),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ).opacity(0.85),
 | 
			
		||||
                      onTap: () {
 | 
			
		||||
                        launchUrlString(_article!.url);
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Gap(MediaQuery.of(context).padding.bottom),
 | 
			
		||||
                  ],
 | 
			
		||||
                ).padding(horizontal: 12, vertical: 16),
 | 
			
		||||
              ),
 | 
			
		||||
              child: Container(
 | 
			
		||||
                constraints: BoxConstraints(maxWidth: 640),
 | 
			
		||||
                child: SingleChildScrollView(
 | 
			
		||||
                  child: Column(
 | 
			
		||||
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                    spacing: 8,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
 | 
			
		||||
                      Builder(builder: (context) {
 | 
			
		||||
                        final htmlDescription = parse(_article!.description);
 | 
			
		||||
                        return Text(
 | 
			
		||||
                          htmlDescription.children.map((ele) => ele.text.trim()).join(),
 | 
			
		||||
                          style: Theme.of(context).textTheme.bodyMedium,
 | 
			
		||||
                        );
 | 
			
		||||
                      }),
 | 
			
		||||
                      Builder(builder: (context) {
 | 
			
		||||
                        final date = _article!.publishedAt ?? _article!.createdAt;
 | 
			
		||||
                        return Row(
 | 
			
		||||
                          spacing: 2,
 | 
			
		||||
                          children: [
 | 
			
		||||
                            Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                            Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
 | 
			
		||||
                            Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                          ],
 | 
			
		||||
                        ).opacity(0.75);
 | 
			
		||||
                      }),
 | 
			
		||||
                      Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
 | 
			
		||||
                      const Divider(),
 | 
			
		||||
                      ..._parseHtmlToWidgets(_articleFragment!.children),
 | 
			
		||||
                      const Divider(),
 | 
			
		||||
                      InkWell(
 | 
			
		||||
                        child: Row(
 | 
			
		||||
                          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                          children: [
 | 
			
		||||
                            Text(
 | 
			
		||||
                              'Reference from original website',
 | 
			
		||||
                              style: TextStyle(decoration: TextDecoration.underline),
 | 
			
		||||
                            ),
 | 
			
		||||
                            const Gap(4),
 | 
			
		||||
                            Icon(Icons.launch, size: 16),
 | 
			
		||||
                          ],
 | 
			
		||||
                        ).opacity(0.85),
 | 
			
		||||
                        onTap: () {
 | 
			
		||||
                          launchUrlString(_article!.url);
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                      Gap(MediaQuery.of(context).padding.bottom),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ).padding(horizontal: 12, vertical: 16),
 | 
			
		||||
                ),
 | 
			
		||||
              ).center(),
 | 
			
		||||
            )
 | 
			
		||||
          else if (_article != null)
 | 
			
		||||
            Expanded(
 | 
			
		||||
 
 | 
			
		||||
@@ -70,11 +70,16 @@ class _NewsScreenState extends State<NewsScreen> {
 | 
			
		||||
                sliver: SliverAppBar(
 | 
			
		||||
                  leading: AutoAppBarLeading(),
 | 
			
		||||
                  title: Text('screenNews').tr(),
 | 
			
		||||
                  floating: true,
 | 
			
		||||
                  snap: true,
 | 
			
		||||
                  bottom: TabBar(
 | 
			
		||||
                    isScrollable: true,
 | 
			
		||||
                    tabs: [
 | 
			
		||||
                      Tab(child: Text('newsAllSources'.tr())),
 | 
			
		||||
                      for (final source in _sources!) Tab(child: Text(source.label)),
 | 
			
		||||
                      Tab(child: Text('newsAllSources'.tr()).textColor(Theme.of(context).appBarTheme.foregroundColor)),
 | 
			
		||||
                      for (final source in _sources!)
 | 
			
		||||
                        Tab(
 | 
			
		||||
                          child: Text(source.label).textColor(Theme.of(context).appBarTheme.foregroundColor),
 | 
			
		||||
                        ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
@@ -146,80 +151,87 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> {
 | 
			
		||||
    return MediaQuery.removePadding(
 | 
			
		||||
      context: context,
 | 
			
		||||
      removeTop: true,
 | 
			
		||||
      child: RefreshIndicator(
 | 
			
		||||
        onRefresh: _fetchArticles,
 | 
			
		||||
        child: InfiniteList(
 | 
			
		||||
          isLoading: _isBusy,
 | 
			
		||||
          itemCount: _articles.length,
 | 
			
		||||
          hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
 | 
			
		||||
          onFetchData: () {
 | 
			
		||||
            _fetchArticles();
 | 
			
		||||
          },
 | 
			
		||||
          itemBuilder: (context, index) {
 | 
			
		||||
            final article = _articles[index];
 | 
			
		||||
      child: Center(
 | 
			
		||||
        child: Container(
 | 
			
		||||
          constraints: BoxConstraints(maxWidth: 640),
 | 
			
		||||
          child: RefreshIndicator(
 | 
			
		||||
            onRefresh: _fetchArticles,
 | 
			
		||||
            child: InfiniteList(
 | 
			
		||||
              isLoading: _isBusy,
 | 
			
		||||
              itemCount: _articles.length,
 | 
			
		||||
              hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
 | 
			
		||||
              onFetchData: () {
 | 
			
		||||
                _fetchArticles();
 | 
			
		||||
              },
 | 
			
		||||
              itemBuilder: (context, index) {
 | 
			
		||||
                final article = _articles[index];
 | 
			
		||||
 | 
			
		||||
            final baseUri = Uri.parse(article.url);
 | 
			
		||||
            final baseUrl = '${baseUri.scheme}://${baseUri.host}';
 | 
			
		||||
                final baseUri = Uri.parse(article.url);
 | 
			
		||||
                final baseUrl = '${baseUri.scheme}://${baseUri.host}';
 | 
			
		||||
 | 
			
		||||
            final htmlDescription = parse(article.description);
 | 
			
		||||
            final date = article.publishedAt ?? article.createdAt;
 | 
			
		||||
                final htmlDescription = parse(article.description);
 | 
			
		||||
                final date = article.publishedAt ?? article.createdAt;
 | 
			
		||||
 | 
			
		||||
            return Card(
 | 
			
		||||
              child: InkWell(
 | 
			
		||||
                radius: 8,
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  GoRouter.of(context).pushNamed(
 | 
			
		||||
                    'newsDetail',
 | 
			
		||||
                    pathParameters: {'hash': article.hash},
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
 | 
			
		||||
                      ClipRRect(
 | 
			
		||||
                        borderRadius: BorderRadius.only(
 | 
			
		||||
                          topRight: Radius.circular(8),
 | 
			
		||||
                          topLeft: Radius.circular(8),
 | 
			
		||||
                        ),
 | 
			
		||||
                        child: AspectRatio(
 | 
			
		||||
                          aspectRatio: 16 / 9,
 | 
			
		||||
                          child: Container(
 | 
			
		||||
                            color: Theme.of(context).colorScheme.surfaceContainer,
 | 
			
		||||
                            child: AutoResizeUniversalImage(
 | 
			
		||||
                              article.thumbnail.startsWith('http') ? article.thumbnail : '$baseUrl/${article.thumbnail}',
 | 
			
		||||
                return Card(
 | 
			
		||||
                  child: InkWell(
 | 
			
		||||
                    radius: 8,
 | 
			
		||||
                    onTap: () {
 | 
			
		||||
                      GoRouter.of(context).pushNamed(
 | 
			
		||||
                        'newsDetail',
 | 
			
		||||
                        pathParameters: {'hash': article.hash},
 | 
			
		||||
                      );
 | 
			
		||||
                    },
 | 
			
		||||
                    child: Column(
 | 
			
		||||
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
 | 
			
		||||
                          ClipRRect(
 | 
			
		||||
                            borderRadius: BorderRadius.only(
 | 
			
		||||
                              topRight: Radius.circular(8),
 | 
			
		||||
                              topLeft: Radius.circular(8),
 | 
			
		||||
                            ),
 | 
			
		||||
                            child: AspectRatio(
 | 
			
		||||
                              aspectRatio: 16 / 9,
 | 
			
		||||
                              child: Container(
 | 
			
		||||
                                color: Theme.of(context).colorScheme.surfaceContainer,
 | 
			
		||||
                                child: AutoResizeUniversalImage(
 | 
			
		||||
                                  article.thumbnail.startsWith('http')
 | 
			
		||||
                                      ? article.thumbnail
 | 
			
		||||
                                      : '$baseUrl/${article.thumbnail}',
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    const Gap(16),
 | 
			
		||||
                    Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
 | 
			
		||||
                    const Gap(8),
 | 
			
		||||
                    Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
 | 
			
		||||
                        .textStyle(Theme.of(context).textTheme.bodyMedium!)
 | 
			
		||||
                        .padding(horizontal: 16),
 | 
			
		||||
                    const Gap(8),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      spacing: 2,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(widget.allSources.where((x) => x.id == article.source).first.label)
 | 
			
		||||
                            .textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                        const Gap(16),
 | 
			
		||||
                        Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
 | 
			
		||||
                        const Gap(8),
 | 
			
		||||
                        Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
 | 
			
		||||
                            .textStyle(Theme.of(context).textTheme.bodyMedium!)
 | 
			
		||||
                            .padding(horizontal: 16),
 | 
			
		||||
                        const Gap(8),
 | 
			
		||||
                        Row(
 | 
			
		||||
                          spacing: 2,
 | 
			
		||||
                          children: [
 | 
			
		||||
                            Text(widget.allSources.where((x) => x.id == article.source).first.label)
 | 
			
		||||
                                .textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                          ],
 | 
			
		||||
                        ).opacity(0.75).padding(horizontal: 16),
 | 
			
		||||
                        Row(
 | 
			
		||||
                          spacing: 2,
 | 
			
		||||
                          children: [
 | 
			
		||||
                            Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                            Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
 | 
			
		||||
                            Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                          ],
 | 
			
		||||
                        ).opacity(0.75).padding(horizontal: 16),
 | 
			
		||||
                        const Gap(16),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ).opacity(0.75).padding(horizontal: 16),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      spacing: 2,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                        Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
 | 
			
		||||
                        Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ).opacity(0.75).padding(horizontal: 16),
 | 
			
		||||
                    const Gap(16),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user