Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: clean up blog layout #15

Merged
merged 6 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions frontend/lib/blog/blog.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:frontend/data/providers/article_provider.dart';
import 'package:frontend/data/providers/auth_provider.dart';
import 'package:frontend/utils/misc.dart';
import 'package:provider/provider.dart';

import 'widgets/article_items.dart';
Expand Down Expand Up @@ -35,9 +36,28 @@ class _BlogPageState extends State<BlogPage> {

@override
Widget build(BuildContext context) {
return ScaffoldPage.scrollable(
padding: const EdgeInsets.all(12),
children: const [BlogArticlesWidget()],
return WebConstrainedLayout(
child: ScaffoldPage.scrollable(children: const [BlogArticlesWidget()]),
);
}
}

class WebConstrainedLayout extends StatelessWidget {
final Widget child;
const WebConstrainedLayout({super.key, required this.child});

@override
Widget build(BuildContext context) {
final borderSide = BorderSide(color: blogColor.withOpacity(0.1));

return Container(
alignment: Alignment.topCenter,
child: Container(
constraints: const BoxConstraints(maxWidth: 1300),
decoration: BoxDecoration(border: Border(left: borderSide, right: borderSide, top: borderSide)),
margin: const EdgeInsets.all(24),
child: child,
),
);
}
}
58 changes: 28 additions & 30 deletions frontend/lib/blog/blog_detail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ class BlogDetail extends StatelessWidget {
final isPostOwner = currentUser != null && owner != null && currentUser.id == owner.id;

final imageHost = article.imageUrl == null ? null : Uri.tryParse(article.imageUrl!)?.host;
const spacing = SizedBox(height: 24);

return Column(
mainAxisSize: MainAxisSize.min,
children: [
PageHeader(
title: Text(article.title, style: const TextStyle(color: Colors.black)),
Expand Down Expand Up @@ -104,35 +104,33 @@ class BlogDetail extends StatelessWidget {
decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey.withOpacity(0.05)))),
),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 200,
height: 150,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: Card(child: imageView(article.imageUrl!))),
if (imageHost != null) ...[
const SizedBox(height: 8),
Text(imageHost, style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 12)),
]
],
),
spacing,
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 400,
height: 250,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: imageView(article.imageUrl!)),
if (imageHost != null) ...[
const SizedBox(height: 8),
Text(imageHost, style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 12)),
]
],
),
const SizedBox(width: 24),
Expanded(
child: Container(
constraints: const BoxConstraints(minHeight: 350),
alignment: Alignment.topRight,
child: MarkdownWidget(data: article.description, shrinkWrap: true),
),
),
const SizedBox(width: 40),
Expanded(
child: Container(
constraints: const BoxConstraints(minHeight: 350),
alignment: Alignment.topRight,
child: MarkdownWidget(data: article.description, shrinkWrap: true),
),
],
),
),
],
),
Container(
height: 40,
Expand All @@ -141,9 +139,9 @@ class BlogDetail extends StatelessWidget {
),
child: Row(
children: [
Text('Last updated ${timeago.format(article.updatedAt)}', style: footerStyle),
Text('Last Updated: ${timeago.format(article.updatedAt)}', style: footerStyle),
const Spacer(),
if (owner != null) Text('Posted by: ${owner.name}', style: footerStyle)
if (owner != null) Text('Author: ${owner.name}', style: footerStyle)
],
),
)
Expand Down
20 changes: 13 additions & 7 deletions frontend/lib/blog/widgets/add_article_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,19 @@ class DottedBorder extends StatelessWidget {
height: 200,
child: Card(
borderRadius: const BorderRadius.all(Radius.circular(5)),
child: Center(
child: Text(
"New Post +",
style: typography.bodyStrong!.copyWith(color: blogColor),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(FluentIcons.page_add, size: 34),
const SizedBox(height: 16),
Text(
"New Post +",
style: typography.bodyStrong!.copyWith(color: blogColor),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
)),
Expand Down
54 changes: 19 additions & 35 deletions frontend/lib/blog/widgets/article_base_layout.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:frontend/blog/blog.dart';
import 'package:frontend/data/models/article.dart';
import 'package:frontend/data/models/user.dart';
import 'package:frontend/data/services.dart';
Expand Down Expand Up @@ -32,46 +33,29 @@ class ArticleBaseLayoutState extends State<ArticleBaseLayout> {

@override
Widget build(BuildContext context) {
final pageWidth = MediaQuery.of(context).size.width * 0.8;

return ScaffoldPage(
padding: EdgeInsets.zero,
content: Stack(
children: [
acrylicBackground,
Center(
child: SizedBox(
width: pageWidth.toDouble(),
child: StreamBuilder<ProviderEvent<Article>>(
stream: _detailProvider.stream,
initialData: _detailProvider.lastEvent,
builder: (context, snapshot) {
return Card(
padding: EdgeInsets.zero,
backgroundColor: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_showingLoading) const SizedBox(width: double.maxFinite, child: ProgressBar()),
Container(
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: widget.child(_detailProvider, this),
),
),
],
),
);
},
),
return WebConstrainedLayout(
child: StreamBuilder<ProviderEvent<Article>>(
stream: _detailProvider.stream,
initialData: _detailProvider.lastEvent,
builder: (_, __) => ScaffoldPage.scrollable(
children: [
if (_showingLoading) const SizedBox(width: double.maxFinite, child: ProgressBar()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 30),
child: widget.child(_detailProvider, this),
),
),
],
],
),
),
);
}

@override
void dispose() {
super.dispose();
_detailProvider.dispose();
}

void setLoading(bool show) => setState(() => _showingLoading = show);

void handleErrors(ProviderEvent<Article> event) {
Expand Down
9 changes: 5 additions & 4 deletions frontend/lib/blog/widgets/article_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ class ArticleCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (article.imageUrl != null) ...[
Expanded(child: imageView(article.imageUrl!)),
] else
Container(color: blogColor.withOpacity(0.1), height: 100),
Expanded(
child: article.imageUrl != null
? imageView(article.imageUrl!)
: Container(color: blogColor.withOpacity(0.1)),
),
const SizedBox(height: 16),
Text(
article.title,
Expand Down
4 changes: 2 additions & 2 deletions frontend/lib/blog/widgets/article_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class _ArticleFormViewState extends State<ArticleFormView> {
InfoLabel(label: 'Write your post'),
const SizedBox(height: 8),
Container(
constraints: const BoxConstraints(maxHeight: 150),
constraints: const BoxConstraints(maxHeight: 400),
width: double.maxFinite,
child: Card(
borderColor: blogColor.withOpacity(0.1),
Expand All @@ -135,7 +135,7 @@ class _ArticleFormViewState extends State<ArticleFormView> {
),
_spacing,
InfoLabel(
label: 'Image Url',
label: 'Image Url (Optional)',
labelStyle: const TextStyle(fontWeight: FontWeight.w300),
child: TextBox(controller: _imageUrlCtrl, keyboardType: TextInputType.url),
),
Expand Down
4 changes: 2 additions & 2 deletions frontend/lib/data/api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class ApiService {
ApiService(this.baseUrl) : client = BrowserClient()..withCredentials = true;

bool get hasAuthCookie {
final cookie = html.document.cookie;
return cookie != null && cookie.contains('auth');
final last = html.document.cookie?.split('auth=').last.trim();
return last != null && last.length > 10;
}

Map<String, String> get _headers => {HttpHeaders.contentTypeHeader: 'application/json'};
Expand Down
22 changes: 19 additions & 3 deletions frontend/lib/data/providers/auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:frontend/data/services.dart';
import 'package:frontend/utils/provider.dart';
import 'package:localstorage/localstorage.dart';
import 'package:meta/meta.dart';

import '../models/user.dart';
Expand All @@ -10,22 +11,30 @@ class AuthProvider extends BaseProvider<User> {
@visibleForTesting
ApiService get apiSvc => getIt.get<ApiService>();

User? get user => lastEvent?.data;
final LocalStorage _userLocalStore = LocalStorage('user_session_store');
static const String userStorageKey = 'user_data';

User? get user {
final userFromState = lastEvent?.data;
if (userFromState != null) return userFromState;
final serializedUser = _userLocalStore.getItem(userStorageKey);
return serializedUser == null ? null : User.fromJson(serializedUser);
}

Future<void> getUser() async {
if (!apiSvc.hasAuthCookie) return;

final user = await safeRun(() => apiSvc.getUser());
if (user == null) return;

addEvent(ProviderEvent.success(data: user));
_setUser(user);
}

Future<void> login(String email, String password) async {
final user = await safeRun(() => apiSvc.loginUser(email, password));
if (user == null) return;

addEvent(ProviderEvent.success(data: user));
_setUser(user);
}

Future<bool> register(String displayName, String email, String password) async {
Expand All @@ -36,8 +45,15 @@ class AuthProvider extends BaseProvider<User> {
return true;
}

void _setUser(User user) {
addEvent(ProviderEvent.success(data: user));

_userLocalStore.setItem(userStorageKey, user.toJson());
}

void logout() {
apiSvc.clearAuthCookie();
_userLocalStore.clear();
addEvent(const ProviderEvent.idle());
}
}
27 changes: 13 additions & 14 deletions frontend/lib/utils/misc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,15 @@ const acrylicBackground = Card(
),
);

loadingView({String message = 'loading, please wait...'}) => Container(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(strokeWidth: 2),
const SizedBox(height: 24),
Text(message, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w300)),
],
),
);
loadingView({String message = 'loading, please wait...', bool showMessage = true, double? size}) => Container(
alignment: Alignment.center,
child: Column(mainAxisSize: MainAxisSize.min, children: [
SizedBox(width: size, height: size, child: const ProgressRing(strokeWidth: 2)),
if (showMessage) ...[
const SizedBox(height: 24),
Text(message, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w300)),
]
]));

errorView({String? message}) {
message ??= 'Oops!, an error occurred';
Expand All @@ -58,10 +56,11 @@ errorView({String? message}) {
);
}

imageView(String imageUrl) => FastCachedImage(
imageView(String imageUrl, {double? width, double? height}) => FastCachedImage(
url: imageUrl,
fit: BoxFit.cover,
width: double.maxFinite,
height: height ?? double.maxFinite,
width: width ?? double.maxFinite,
fadeInDuration: const Duration(seconds: 1),
loadingBuilder: (p0, p1) => loadingView(message: ''),
loadingBuilder: (p0, p1) => loadingView(showMessage: false, size: 24),
);
8 changes: 8 additions & 0 deletions frontend/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
localstorage:
dependency: "direct main"
description:
name: localstorage
sha256: fdff4f717114e992acfd4045dc4a9ab9b987ca57f020965d63e3eb34089c60d8
url: "https://pub.dev"
source: hosted
version: "4.0.1+4"
logging:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions frontend/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies:
fast_cached_network_image: ^1.2.0
markdown_widget:
appflowy_editor: ^2.2.0
localstorage: ^4.0.1+4

dev_dependencies:
flutter_lints: ^3.0.1
Expand Down
Loading