This commit is contained in:
Niels Gorter 2024-01-30 11:20:31 +01:00
parent 80df20c323
commit a7b62c4eb5
14 changed files with 573 additions and 0 deletions

43
packages/widgetbook/.gitignore vendored Normal file
View file

@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View file

@ -0,0 +1,16 @@
# widgetbook
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:flutter_timeline/flutter_timeline.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:timeline_widgetbook/main.directories.g.dart';
import 'package:timeline_widgetbook/mock_timeline_service.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
void main() {
initializeDateFormatting();
runApp(const WidgetBookApp());
}
@widgetbook.App()
class WidgetBookApp extends StatelessWidget {
const WidgetBookApp({super.key});
@override
Widget build(BuildContext context) {
return Widgetbook.material(
integrations: [
WidgetbookCloudIntegration(),
],
addons: [
DeviceFrameAddon(
devices: [
Devices.ios.iPhoneSE,
Devices.ios.iPhone13,
Devices.android.bigPhone,
Devices.android.mediumPhone,
Devices.android.smallPhone,
],
initialDevice: Devices.ios.iPhone13,
),
MaterialThemeAddon(
themes: [
WidgetbookTheme(
name: 'Light',
data: ThemeData.light(),
),
WidgetbookTheme(
name: 'Dark',
data: ThemeData.dark(),
),
],
initialTheme: WidgetbookTheme(
name: 'Light',
data: ThemeData.light(),
),
),
],
directories: directories,
);
}
}
@widgetbook.UseCase(
designLink:
'https://www.figma.com/file/PRJoVXQ5aOjAICfkQdAq2A/Iconica-User-Stories?type=design&node-id=34-2763&mode=design&t=W72P3tkEascAKDCk-4',
name: 'Timeline post screen',
type: TimelinePostScreen,
)
Widget postScreenUseCase(BuildContext context) {
var service = TestTimelineService()..fetchPosts(null);
var options = const TimelineOptions();
return TimelinePostScreen(
userId: '1',
service: service,
options: options,
post: service.posts.first,
onPostDelete: () {},
);
}
@widgetbook.UseCase(
designLink:
'https://www.figma.com/file/PRJoVXQ5aOjAICfkQdAq2A/Iconica-User-Stories?type=design&node-id=34-2763&mode=design&t=W72P3tkEascAKDCk-4',
name: 'Timeline screen',
type: TimelineScreen,
)
Widget timelineUseCase(BuildContext context) {
var service = TestTimelineService()..fetchPosts(null);
var options = const TimelineOptions();
return TimelineScreen(
userId: '1',
options: options,
onPostTap: (_) {},
service: service,
);
}

View file

@ -0,0 +1,39 @@
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_import, prefer_relative_imports, directives_ordering
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// AppGenerator
// **************************************************************************
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:timeline_widgetbook/main.dart' as _i2;
import 'package:widgetbook/widgetbook.dart' as _i1;
final directories = <_i1.WidgetbookNode>[
_i1.WidgetbookFolder(
name: 'screens',
children: [
_i1.WidgetbookLeafComponent(
name: 'TimelinePostScreen',
useCase: _i1.WidgetbookUseCase(
name: 'Timeline post screen',
builder: _i2.postScreenUseCase,
designLink:
'https://www.figma.com/file/PRJoVXQ5aOjAICfkQdAq2A/Iconica-User-Stories?type=design&node-id=34-2763&mode=design&t=W72P3tkEascAKDCk-4',
),
),
_i1.WidgetbookLeafComponent(
name: 'TimelineScreen',
useCase: _i1.WidgetbookUseCase(
name: 'Timeline screen',
builder: _i2.timelineUseCase,
designLink:
'https://www.figma.com/file/PRJoVXQ5aOjAICfkQdAq2A/Iconica-User-Stories?type=design&node-id=34-2763&mode=design&t=W72P3tkEascAKDCk-4',
),
),
],
)
];

View file

@ -0,0 +1,233 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_timeline/flutter_timeline.dart';
import 'package:uuid/uuid.dart';
class TestTimelineService
with ChangeNotifier
implements TimelineService, TimelineUserService {
List<TimelinePost> _posts = [];
List<TimelinePost> get posts => _posts;
@override
Future<TimelinePost> createPost(TimelinePost post) async {
_posts.add(
post.copyWith(
creator: const TimelinePosterUserModel(userId: 'test_user'),
),
);
notifyListeners();
return post;
}
@override
Future<void> deletePost(TimelinePost post) async {
_posts = _posts.where((element) => element.id != post.id).toList();
notifyListeners();
}
@override
Future<TimelinePost> deletePostReaction(
TimelinePost post,
String reactionId,
) async {
if (post.reactions != null && post.reactions!.isNotEmpty) {
var reaction =
post.reactions!.firstWhere((element) => element.id == reactionId);
var updatedPost = post.copyWith(
reaction: post.reaction - 1,
reactions: (post.reactions ?? [])..remove(reaction),
);
_posts = _posts
.map(
(p) => p.id == post.id ? updatedPost : p,
)
.toList();
notifyListeners();
return updatedPost;
}
return post;
}
@override
Future<TimelinePost> fetchPostDetails(TimelinePost post) async {
var reactions = post.reactions ?? [];
var updatedReactions = <TimelinePostReaction>[];
for (var reaction in reactions) {
updatedReactions.add(reaction.copyWith(
creator: const TimelinePosterUserModel(userId: 'test_user')));
}
var updatedPost = post.copyWith(reactions: updatedReactions);
_posts = _posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
notifyListeners();
return updatedPost;
}
@override
Future<List<TimelinePost>> fetchPosts(String? category) async {
var posts = getMockedPosts();
_posts = posts;
notifyListeners();
return posts;
}
@override
Future<List<TimelinePost>> fetchPostsPaginated(
String? category,
int limit,
) async {
notifyListeners();
return _posts;
}
@override
Future<TimelinePost> fetchPost(TimelinePost post) async {
notifyListeners();
return post;
}
@override
Future<List<TimelinePost>> refreshPosts(String? category) async {
var posts = <TimelinePost>[];
_posts = [...posts, ..._posts];
notifyListeners();
return posts;
}
@override
TimelinePost? getPost(String postId) =>
(_posts.any((element) => element.id == postId))
? _posts.firstWhere((element) => element.id == postId)
: null;
@override
List<TimelinePost> getPosts(String? category) => _posts
.where((element) => category == null || element.category == category)
.toList();
@override
Future<TimelinePost> likePost(String userId, TimelinePost post) async {
var updatedPost = post.copyWith(
likes: post.likes + 1,
likedBy: (post.likedBy ?? [])..add(userId),
);
_posts = _posts
.map(
(p) => p.id == post.id ? updatedPost : p,
)
.toList();
notifyListeners();
return updatedPost;
}
@override
Future<TimelinePost> unlikePost(String userId, TimelinePost post) async {
var updatedPost = post.copyWith(
likes: post.likes - 1,
likedBy: post.likedBy?..remove(userId),
);
_posts = _posts
.map(
(p) => p.id == post.id ? updatedPost : p,
)
.toList();
notifyListeners();
return updatedPost;
}
@override
Future<TimelinePost> reactToPost(
TimelinePost post,
TimelinePostReaction reaction, {
Uint8List? image,
}) async {
var reactionId = const Uuid().v4();
var updatedReaction = reaction.copyWith(
id: reactionId,
creator: const TimelinePosterUserModel(userId: 'test_user'));
var updatedPost = post.copyWith(
reaction: post.reaction + 1,
reactions: post.reactions?..add(updatedReaction),
);
_posts = _posts
.map(
(p) => p.id == post.id ? updatedPost : p,
)
.toList();
notifyListeners();
return updatedPost;
}
List<TimelinePost> getMockedPosts() {
return [
for (var i = 0; i < 20; i++) ...[
if (i == 0) ...[
TimelinePost(
id: 'Post$i',
creatorId: 'test_user',
title: 'Post $i',
category: 'text',
content: "Post $i content",
likes: i,
reaction: 0,
reactions: getMockedReactions('Post$i'),
createdAt: DateTime.now().subtract(Duration(days: i % 10)),
reactionEnabled: true,
imageUrl: 'https://picsum.photos/seed/$i/200/300',
)
] else ...[
TimelinePost(
id: 'Post$i',
creatorId: 'test_user',
title: 'Post $i',
category: 'text',
content: "Post $i content",
likes: i,
reaction: 0,
createdAt: DateTime.now().subtract(Duration(days: i % 10)),
reactionEnabled: false,
imageUrl: 'https://picsum.photos/seed/$i/200/300',
)
],
]
];
}
List<TimelinePostReaction> getMockedReactions(String posdId) {
return [
for (var i = 0; i < 20; i++) ...[
TimelinePostReaction(
id: 'Reaction$i',
postId: posdId,
reaction: 'Reaction $i',
createdAt: DateTime.now().subtract(Duration(days: i % 10)),
creatorId: 'test_user',
imageUrl:
(i % 2 == 0) ? 'https://picsum.photos/seed/$i/200/300' : null,
)
]
];
}
@override
Future<TimelinePosterUserModel?> getUser(String userId) async {
return TimelinePosterUserModel(
userId: userId,
);
}
@override
set posts(List<TimelinePost> posts) {
_posts = posts;
notifyListeners();
}
}

View file

@ -0,0 +1,29 @@
name: timeline_widgetbook
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.2.5 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
widgetbook_annotation: ^3.1.0
widgetbook: ^3.7.1
flutter_timeline:
path: ../flutter_timeline
intl: ^0.19.0
dev_dependencies:
build_runner: any
widgetbook_generator: ^3.7.0
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="widgetbook">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>widgetbook</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
{
"name": "widgetbook",
"short_name": "widgetbook",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}