Remove visual samples index (#2607)
Removes VSI and all related infra. It has been replaced by [an index page on flutter.dev](https://docs.flutter.dev/reference/learning-resources) Fixes: #2582 #2507 ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I have added sample code updates to the [changelog]. - [x] I updated/added relevant documentation (doc comments with `///`).
38
.github/workflows/gh-pages.yml
vendored
@@ -1,38 +0,0 @@
|
|||||||
name: Deploy to GitHub Pages
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
build-and-deploy:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'flutter/samples'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
|
||||||
|
|
||||||
- name: Init scripts
|
|
||||||
run: dart pub get
|
|
||||||
working-directory: web/_tool
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: dart run _tool/build_ci.dart
|
|
||||||
working-directory: web
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
publish_dir: web/samples_index/public
|
|
||||||
11
.github/workflows/main.yml
vendored
@@ -37,17 +37,6 @@ jobs:
|
|||||||
channel: ${{ matrix.flutter_version }}
|
channel: ${{ matrix.flutter_version }}
|
||||||
- run: ./tool/flutter_ci_script_${{ matrix.flutter_version }}.sh
|
- run: ./tool/flutter_ci_script_${{ matrix.flutter_version }}.sh
|
||||||
|
|
||||||
web-samples-index:
|
|
||||||
name: web/samples_index config check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
|
||||||
- run: |
|
|
||||||
dart pub get
|
|
||||||
dart run grinder generate
|
|
||||||
working-directory: web/samples_index
|
|
||||||
|
|
||||||
# android-build:
|
# android-build:
|
||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# if: github.repository == 'flutter/samples'
|
# if: github.repository == 'flutter/samples'
|
||||||
|
|||||||
36
.github/workflows/verify-web-demos.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: Verify web demos
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify-web-demos:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'flutter/samples'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
flutter_version:
|
|
||||||
- stable
|
|
||||||
# TODO(johnpryan): https://github.com/flutter/samples/issues/1469
|
|
||||||
# - beta
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
|
||||||
with:
|
|
||||||
channel: ${{ matrix.flutter_version }}
|
|
||||||
- name: Init scripts
|
|
||||||
run: dart pub get
|
|
||||||
working-directory: web/_tool
|
|
||||||
- name: Verify packages
|
|
||||||
run: dart run _tool/verify_packages.dart
|
|
||||||
working-directory: web
|
|
||||||
@@ -64,9 +64,6 @@ declare -ar PROJECT_NAMES=(
|
|||||||
"testing_app"
|
"testing_app"
|
||||||
"veggieseasons"
|
"veggieseasons"
|
||||||
"web_embedding/element_embedding_demo"
|
"web_embedding/element_embedding_demo"
|
||||||
"web/_tool"
|
|
||||||
# TODO(ewindmill): dart:html is deprecated. Delete samples_index
|
|
||||||
# "web/samples_index"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ci_projects "beta" "${PROJECT_NAMES[@]}"
|
ci_projects "beta" "${PROJECT_NAMES[@]}"
|
||||||
|
|||||||
@@ -67,9 +67,6 @@ declare -ar PROJECT_NAMES=(
|
|||||||
"testing_app"
|
"testing_app"
|
||||||
"veggieseasons"
|
"veggieseasons"
|
||||||
"web_embedding/element_embedding_demo"
|
"web_embedding/element_embedding_demo"
|
||||||
"web/_tool"
|
|
||||||
# TODO(ewindmill): dart:html is deprecated. Delete samples_index
|
|
||||||
# "web/samples_index"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ci_projects "master" "${PROJECT_NAMES[@]}"
|
ci_projects "master" "${PROJECT_NAMES[@]}"
|
||||||
|
|||||||
@@ -61,9 +61,6 @@ declare -ar PROJECT_NAMES=(
|
|||||||
"testing_app"
|
"testing_app"
|
||||||
"veggieseasons"
|
"veggieseasons"
|
||||||
"web_embedding/element_embedding_demo"
|
"web_embedding/element_embedding_demo"
|
||||||
"web/_tool"
|
|
||||||
# TODO(ewindmill): dart:html is deprecated. Delete samples_index
|
|
||||||
# "web/samples_index"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ci_projects "stable" "${PROJECT_NAMES[@]}"
|
ci_projects "stable" "${PROJECT_NAMES[@]}"
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ declare -ar PROJECT_NAMES=(
|
|||||||
"testing_app"
|
"testing_app"
|
||||||
"veggieseasons"
|
"veggieseasons"
|
||||||
"web_embedding/element_embedding_demo"
|
"web_embedding/element_embedding_demo"
|
||||||
"web/_tool"
|
|
||||||
"web/samples_index"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "--- Running flutter clean and flutter pub get for each sample ---"
|
echo "--- Running flutter clean and flutter pub get for each sample ---"
|
||||||
|
|||||||
1
web/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
*/build
|
|
||||||
29
web/_packages/web_startup_analyzer/.gitignore
vendored
@@ -1,29 +0,0 @@
|
|||||||
# 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
|
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
|
||||||
/pubspec.lock
|
|
||||||
**/doc/api/
|
|
||||||
.dart_tool/
|
|
||||||
build/
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
|
||||||
#
|
|
||||||
# This file should be version controlled and should not be manually edited.
|
|
||||||
|
|
||||||
version:
|
|
||||||
revision: "f5fb61b953a631f47191124a31169701911ee1f4"
|
|
||||||
channel: "main"
|
|
||||||
|
|
||||||
project_type: package
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
|
||||||
#
|
|
||||||
# This file should be version controlled and should not be manually edited.
|
|
||||||
|
|
||||||
version:
|
|
||||||
revision: "f5fb61b953a631f47191124a31169701911ee1f4"
|
|
||||||
channel: "main"
|
|
||||||
|
|
||||||
project_type: app
|
|
||||||
|
|
||||||
# Tracks metadata for the flutter migrate command
|
|
||||||
migration:
|
|
||||||
platforms:
|
|
||||||
- platform: root
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
- platform: android
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
- platform: ios
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
- platform: linux
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
- platform: macos
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
- platform: web
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
- platform: windows
|
|
||||||
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
|
|
||||||
|
|
||||||
# User provided section
|
|
||||||
|
|
||||||
# List of Local paths (relative to this file) that should be
|
|
||||||
# ignored by the migrate tool.
|
|
||||||
#
|
|
||||||
# Files that are not part of the templates will be ignored by default.
|
|
||||||
unmanaged_files:
|
|
||||||
- 'lib/main.dart'
|
|
||||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
linter:
|
|
||||||
rules:
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
// ignore_for_file: avoid_print
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:web_startup_analyzer/web_startup_analyzer.dart';
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
var analyzer = WebStartupAnalyzer(additionalFrameCount: 10);
|
|
||||||
print(json.encode(analyzer.startupTiming));
|
|
||||||
analyzer.onFirstFrame.addListener(() {
|
|
||||||
print(json.encode({'firstFrame': analyzer.onFirstFrame.value}));
|
|
||||||
});
|
|
||||||
analyzer.onFirstPaint.addListener(() {
|
|
||||||
print(
|
|
||||||
json.encode({
|
|
||||||
'firstPaint': analyzer.onFirstPaint.value?.$1,
|
|
||||||
'firstContentfulPaint': analyzer.onFirstPaint.value?.$2,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
analyzer.onAdditionalFrames.addListener(() {
|
|
||||||
print(json.encode({'additionalFrames': analyzer.onAdditionalFrames.value}));
|
|
||||||
});
|
|
||||||
runApp(WebStartupAnalyzerSample(analyzer: analyzer));
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebStartupAnalyzerSample extends StatelessWidget {
|
|
||||||
final WebStartupAnalyzer analyzer;
|
|
||||||
const WebStartupAnalyzerSample({super.key, required this.analyzer});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MaterialApp(
|
|
||||||
title: 'Flutter web app timing',
|
|
||||||
theme: ThemeData(
|
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green.shade100),
|
|
||||||
),
|
|
||||||
home: WebStartupAnalyzerScreen(analyzer: analyzer),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebStartupAnalyzerScreen extends StatefulWidget {
|
|
||||||
final WebStartupAnalyzer analyzer;
|
|
||||||
|
|
||||||
const WebStartupAnalyzerScreen({super.key, required this.analyzer});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<WebStartupAnalyzerScreen> createState() =>
|
|
||||||
_WebStartupAnalyzerScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WebStartupAnalyzerScreenState extends State<WebStartupAnalyzerScreen> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.amber.shade50,
|
|
||||||
body: Align(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.all(8.0),
|
|
||||||
constraints: const BoxConstraints(maxWidth: 400),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
child: ListenableBuilder(
|
|
||||||
listenable: widget.analyzer.onChange,
|
|
||||||
builder: (BuildContext context, child) {
|
|
||||||
return ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: [
|
|
||||||
TimingWidget(
|
|
||||||
name: 'DCL',
|
|
||||||
timingMs: widget.analyzer.domContentLoaded,
|
|
||||||
),
|
|
||||||
TimingWidget(
|
|
||||||
name: 'Load entrypoint',
|
|
||||||
timingMs: widget.analyzer.loadEntrypoint,
|
|
||||||
),
|
|
||||||
TimingWidget(
|
|
||||||
name: 'Initialize engine',
|
|
||||||
timingMs: widget.analyzer.initializeEngine,
|
|
||||||
),
|
|
||||||
TimingWidget(
|
|
||||||
name: 'Run app',
|
|
||||||
timingMs: widget.analyzer.appRunnerRunApp,
|
|
||||||
),
|
|
||||||
if (widget.analyzer.firstFrame != null)
|
|
||||||
TimingWidget(
|
|
||||||
name: 'First frame',
|
|
||||||
timingMs: widget.analyzer.firstFrame!,
|
|
||||||
),
|
|
||||||
if (widget.analyzer.firstPaint != null)
|
|
||||||
TimingWidget(
|
|
||||||
name: 'First paint',
|
|
||||||
timingMs: widget.analyzer.firstPaint!,
|
|
||||||
),
|
|
||||||
if (widget.analyzer.firstContentfulPaint != null)
|
|
||||||
TimingWidget(
|
|
||||||
name: 'First contentful paint',
|
|
||||||
timingMs: widget.analyzer.firstContentfulPaint!,
|
|
||||||
),
|
|
||||||
if (widget.analyzer.additionalFrames != null) ...[
|
|
||||||
for (var i in widget.analyzer.additionalFrames!)
|
|
||||||
TimingWidget(name: 'Frame', timingMs: i.toDouble()),
|
|
||||||
] else
|
|
||||||
TextButton(
|
|
||||||
child: const Text('Trigger frames'),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimingWidget extends StatelessWidget {
|
|
||||||
final String name;
|
|
||||||
final double timingMs;
|
|
||||||
|
|
||||||
const TimingWidget({super.key, required this.name, required this.timingMs});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text(
|
|
||||||
name,
|
|
||||||
style: const TextStyle(fontSize: 18),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
trailing: Text(
|
|
||||||
'${timingMs.truncate()}ms',
|
|
||||||
style: const TextStyle(fontSize: 18),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
name: example
|
|
||||||
description: "flutter_web_startup_analyzer example"
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
version: 1.0.0+1
|
|
||||||
environment:
|
|
||||||
sdk: ^3.7.0-0
|
|
||||||
flutter: ^3.16.0
|
|
||||||
dependencies:
|
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
cupertino_icons: ^1.0.6
|
|
||||||
web_startup_analyzer:
|
|
||||||
path: ../
|
|
||||||
dev_dependencies:
|
|
||||||
flutter_test:
|
|
||||||
sdk: flutter
|
|
||||||
flutter_lints: ^5.0.0
|
|
||||||
flutter:
|
|
||||||
uses-material-design: true
|
|
||||||
|
Before Width: | Height: | Size: 917 B |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,50 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base href="$FLUTTER_BASE_HREF">
|
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
|
||||||
<meta name="description" content="web_startup_analyzer example">
|
|
||||||
|
|
||||||
<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="example">
|
|
||||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
|
||||||
|
|
||||||
<title>web_perf_metrics example</title>
|
|
||||||
<link rel="manifest" href="manifest.json">
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const serviceWorkerVersion = null;
|
|
||||||
</script>
|
|
||||||
<script src="flutter.js" defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script type="text/javascript" src="assets/packages/web_startup_analyzer/lib/web_startup_analyzer.js"></script>
|
|
||||||
<script>
|
|
||||||
var flutterWebStartupAnalyzer = new FlutterWebStartupAnalyzer();
|
|
||||||
var analyzer = flutterWebStartupAnalyzer;
|
|
||||||
|
|
||||||
window.addEventListener('load', function(ev) {
|
|
||||||
analyzer.markStart("loadEntrypoint");
|
|
||||||
_flutter.loader.loadEntrypoint({
|
|
||||||
serviceWorker: {
|
|
||||||
serviceWorkerVersion: serviceWorkerVersion,
|
|
||||||
},
|
|
||||||
onEntrypointLoaded: function(engineInitializer) {
|
|
||||||
analyzer.markFinished("loadEntrypoint");
|
|
||||||
analyzer.markStart("initializeEngine");
|
|
||||||
engineInitializer.initializeEngine().then(function(appRunner) {
|
|
||||||
analyzer.markFinished("initializeEngine");
|
|
||||||
analyzer.markStart("appRunnerRunApp");
|
|
||||||
appRunner.runApp();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "example",
|
|
||||||
"short_name": "example",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
class FrameAnalyzer {
|
|
||||||
final WidgetsBinding _binding;
|
|
||||||
final Completer _onDone = Completer();
|
|
||||||
int _remainingFrames;
|
|
||||||
|
|
||||||
final int additionalFrames;
|
|
||||||
List<int> additionalFrameTimes = [];
|
|
||||||
|
|
||||||
FrameAnalyzer(this._binding, {this.additionalFrames = 10})
|
|
||||||
: _remainingFrames = additionalFrames;
|
|
||||||
|
|
||||||
Future<void> captureAdditionalFrames() {
|
|
||||||
_binding.addTimingsCallback(_timingsCallback);
|
|
||||||
return _onDone.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _reportFrame(FrameTiming frameTiming) {
|
|
||||||
additionalFrameTimes.add(frameTiming.totalSpan.inMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _timingsCallback(List<FrameTiming> timings) {
|
|
||||||
int i = 0;
|
|
||||||
while (_remainingFrames > 0 && i < timings.length) {
|
|
||||||
_reportFrame(timings[i]);
|
|
||||||
i++;
|
|
||||||
_remainingFrames--;
|
|
||||||
}
|
|
||||||
if (_remainingFrames <= 0) {
|
|
||||||
_binding.removeTimingsCallback(_timingsCallback);
|
|
||||||
|
|
||||||
_onDone.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:js_interop';
|
|
||||||
|
|
||||||
@JS()
|
|
||||||
@staticInterop
|
|
||||||
external FlutterWebStartupAnalyzer get flutterWebStartupAnalyzer;
|
|
||||||
|
|
||||||
@JS()
|
|
||||||
@staticInterop
|
|
||||||
class FlutterWebStartupAnalyzer {
|
|
||||||
external factory FlutterWebStartupAnalyzer();
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FlutterWebStartupAnalyzerExtensions on FlutterWebStartupAnalyzer {
|
|
||||||
external JSObject get timings;
|
|
||||||
external void markStart(String name);
|
|
||||||
external void markFinished(String name);
|
|
||||||
external void capture(String name);
|
|
||||||
external void captureAll();
|
|
||||||
external void capturePaint();
|
|
||||||
external void report();
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:js_interop';
|
|
||||||
import 'dart:js_interop_unsafe';
|
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:web_startup_analyzer/src/web_startup_analyzer_base.dart';
|
|
||||||
|
|
||||||
import 'frame_analyzer.dart';
|
|
||||||
import 'startup_analyzer.dart';
|
|
||||||
|
|
||||||
class WebStartupAnalyzer extends WebStartupAnalyzerBase {
|
|
||||||
final WidgetsBinding _widgetsBinding;
|
|
||||||
late final FrameAnalyzer _frameAnalyzer;
|
|
||||||
List<int>? _additionalFrames;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> startupTiming = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get domContentLoaded =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['domContentLoaded'] as JSNumber)
|
|
||||||
.toDartDouble;
|
|
||||||
@override
|
|
||||||
double get loadEntrypoint =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['loadEntrypoint'] as JSNumber)
|
|
||||||
.toDartDouble;
|
|
||||||
@override
|
|
||||||
double get initializeEngine =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['initializeEngine'] as JSNumber)
|
|
||||||
.toDartDouble;
|
|
||||||
@override
|
|
||||||
double get appRunnerRunApp =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['appRunnerRunApp'] as JSNumber)
|
|
||||||
.toDartDouble;
|
|
||||||
@override
|
|
||||||
double? get firstFrame =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['firstFrame'] as JSNumber?)
|
|
||||||
?.toDartDouble;
|
|
||||||
@override
|
|
||||||
double? get firstPaint =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['first-paint'] as JSNumber?)
|
|
||||||
?.toDartDouble;
|
|
||||||
@override
|
|
||||||
double? get firstContentfulPaint =>
|
|
||||||
(flutterWebStartupAnalyzer.timings['first-contentful-paint'] as JSNumber?)
|
|
||||||
?.toDartDouble;
|
|
||||||
@override
|
|
||||||
List<int>? get additionalFrames => _additionalFrames;
|
|
||||||
|
|
||||||
WebStartupAnalyzer({int additionalFrameCount = 5})
|
|
||||||
: _widgetsBinding = WidgetsFlutterBinding.ensureInitialized() {
|
|
||||||
_frameAnalyzer = FrameAnalyzer(
|
|
||||||
_widgetsBinding,
|
|
||||||
additionalFrames: additionalFrameCount,
|
|
||||||
);
|
|
||||||
_captureStartupMetrics();
|
|
||||||
startupTiming = {
|
|
||||||
'domContentLoaded': domContentLoaded,
|
|
||||||
'loadEntrypoint': loadEntrypoint,
|
|
||||||
'initializeEngine': initializeEngine,
|
|
||||||
'appRunnerRunApp': appRunnerRunApp,
|
|
||||||
};
|
|
||||||
_captureFirstFrame().then((value) {
|
|
||||||
flutterWebStartupAnalyzer.captureAll();
|
|
||||||
onFirstFrame.value = firstFrame;
|
|
||||||
|
|
||||||
// Capture first-paint and first-contentful-paint
|
|
||||||
Future.delayed(const Duration(milliseconds: 200)).then((_) {
|
|
||||||
flutterWebStartupAnalyzer.capturePaint();
|
|
||||||
onFirstPaint.value = (firstPaint!, firstContentfulPaint!);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
captureFlutterFrameData().then((value) {
|
|
||||||
_additionalFrames = value;
|
|
||||||
onAdditionalFrames.value = value;
|
|
||||||
});
|
|
||||||
onChange = Listenable.merge([
|
|
||||||
onFirstFrame,
|
|
||||||
onFirstPaint,
|
|
||||||
onAdditionalFrames,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_captureStartupMetrics() {
|
|
||||||
flutterWebStartupAnalyzer.markFinished('appRunnerRunApp');
|
|
||||||
flutterWebStartupAnalyzer.captureAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _captureFirstFrame() {
|
|
||||||
final completer = Completer();
|
|
||||||
flutterWebStartupAnalyzer.markStart('firstFrame');
|
|
||||||
_widgetsBinding.addPostFrameCallback((timeStamp) {
|
|
||||||
flutterWebStartupAnalyzer.markFinished('firstFrame');
|
|
||||||
flutterWebStartupAnalyzer.capture('firstFrame');
|
|
||||||
completer.complete();
|
|
||||||
});
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> captureFlutterFrameData() async {
|
|
||||||
await _frameAnalyzer.captureAdditionalFrames();
|
|
||||||
return _frameAnalyzer.additionalFrameTimes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
// Base class for the web (real) implementation and dart:io (stub)
|
|
||||||
// implementation.
|
|
||||||
abstract class WebStartupAnalyzerBase {
|
|
||||||
late final Listenable onChange;
|
|
||||||
ValueNotifier<double?> onFirstFrame = ValueNotifier(null);
|
|
||||||
ValueNotifier<(double, double)?> onFirstPaint = ValueNotifier(null);
|
|
||||||
ValueNotifier<List<int>?> onAdditionalFrames = ValueNotifier(null);
|
|
||||||
|
|
||||||
double get domContentLoaded;
|
|
||||||
double get loadEntrypoint;
|
|
||||||
double get initializeEngine;
|
|
||||||
double get appRunnerRunApp;
|
|
||||||
double? get firstFrame;
|
|
||||||
double? get firstPaint;
|
|
||||||
double? get firstContentfulPaint;
|
|
||||||
List<int>? get additionalFrames;
|
|
||||||
|
|
||||||
Map<String, dynamic> get startupTiming;
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'web_startup_analyzer_base.dart';
|
|
||||||
|
|
||||||
// This class is a stub so that unit tests can run without importing
|
|
||||||
// dart:js_interop and related packages.
|
|
||||||
class WebStartupAnalyzer extends WebStartupAnalyzerBase {
|
|
||||||
WebStartupAnalyzer({int additionalFrameCount = 0});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<int>? get additionalFrames => [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get appRunnerRunApp => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get domContentLoaded => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double? get firstContentfulPaint => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double? get firstFrame => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double? get firstPaint => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get initializeEngine => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get loadEntrypoint => 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> get startupTiming => {};
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
export 'src/web_startup_analyzer_io.dart'
|
|
||||||
if (dart.library.js_interop) 'src/web_startup_analyzer.dart';
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
// Helper class to capture Flutter web app startup timing information
|
|
||||||
class FlutterWebStartupAnalyzer {
|
|
||||||
timings;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.timings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
markStart(name) {
|
|
||||||
this.timings[name] = null;
|
|
||||||
performance.mark('flt-' + name + '-started');
|
|
||||||
}
|
|
||||||
markFinished(name) {
|
|
||||||
performance.mark('flt-' + name + '-finished');
|
|
||||||
}
|
|
||||||
capture(name) {
|
|
||||||
var timingName = 'flt-' + name;
|
|
||||||
var started = 'flt-' + name + 'started';
|
|
||||||
try {
|
|
||||||
var measurement = performance.measure('flt-' + name, 'flt-' + name + '-started', 'flt-' + name + '-finished');
|
|
||||||
} catch(e) {
|
|
||||||
// ignore errors if the mark doesn't exist
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.timings[name] = measurement.duration;
|
|
||||||
}
|
|
||||||
captureAll() {
|
|
||||||
for (var [key, value] of Object.entries(this.timings)) {
|
|
||||||
this.capture(key);
|
|
||||||
}
|
|
||||||
// Capture
|
|
||||||
this.timings['load'] = performance.timing.loadEventEnd - performance.timing.domContentLoadedEventEnd;
|
|
||||||
this.timings['domContentLoaded'] = performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
capturePaint() {
|
|
||||||
const entries = performance.getEntriesByType("paint");
|
|
||||||
// Collect first-paint and first-contentful-paint entries
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
this.timings[entry.name] = entry.startTime;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
name: web_startup_analyzer
|
|
||||||
description: "Captures web startup timing data in a Flutter web app"
|
|
||||||
version: 0.1.0-wip
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ^3.7.0-0
|
|
||||||
flutter: ^3.16.0
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
flutter_test:
|
|
||||||
sdk: flutter
|
|
||||||
flutter_lints: ^5.0.0
|
|
||||||
flutter:
|
|
||||||
assets:
|
|
||||||
- lib/web_startup_analyzer.js
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Copyright {{ year }} The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
include: package:lints/recommended.yaml
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// Copyright 2021 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
import 'common.dart';
|
|
||||||
import 'fix_base_tags.dart';
|
|
||||||
|
|
||||||
const ignoredDirectories = [
|
|
||||||
'_tool',
|
|
||||||
'_packages/web_startup_analyzer',
|
|
||||||
'_packages/web_startup_analyzer/example',
|
|
||||||
'samples_index',
|
|
||||||
];
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
final packageDirs = [
|
|
||||||
...listPackageDirs(Directory.current)
|
|
||||||
.map((path) => p.relative(path, from: Directory.current.path))
|
|
||||||
.where((path) => !ignoredDirectories.contains(path)),
|
|
||||||
];
|
|
||||||
|
|
||||||
print('Building the sample index...');
|
|
||||||
await _run('samples_index', 'flutter', ['pub', 'get']);
|
|
||||||
await _run('samples_index', 'flutter', ['pub', 'run', 'grinder', 'deploy']);
|
|
||||||
|
|
||||||
// Create the directory each Flutter Web sample lives in
|
|
||||||
Directory(
|
|
||||||
p.join(Directory.current.path, 'samples_index', 'public', 'web'),
|
|
||||||
).createSync(recursive: true);
|
|
||||||
|
|
||||||
for (var i = 0; i < packageDirs.length; i++) {
|
|
||||||
var directory = packageDirs[i];
|
|
||||||
|
|
||||||
logWrapped(ansiMagenta, '\n$directory (${i + 1} of ${packageDirs.length})');
|
|
||||||
|
|
||||||
// Create the target directory
|
|
||||||
var directoryName = p.basename(directory);
|
|
||||||
var sourceBuildDir = p.join(
|
|
||||||
Directory.current.path,
|
|
||||||
directory,
|
|
||||||
'build',
|
|
||||||
'web',
|
|
||||||
);
|
|
||||||
var targetDirectory = p.join(
|
|
||||||
Directory.current.path,
|
|
||||||
'samples_index',
|
|
||||||
'public',
|
|
||||||
'web',
|
|
||||||
directoryName,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the sample and copy the files
|
|
||||||
await _run(directory, 'flutter', ['pub', 'get']);
|
|
||||||
await _run(directory, 'flutter', ['build', 'web', '--wasm']);
|
|
||||||
await _run(directory, 'mv', [sourceBuildDir, targetDirectory]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the <base href> tags in each index.html file
|
|
||||||
await fixBaseTags();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invokes run() and exits if the sub-process failed.
|
|
||||||
Future<void> _run(
|
|
||||||
String workingDir,
|
|
||||||
String commandName,
|
|
||||||
List<String> args,
|
|
||||||
) async {
|
|
||||||
var success = await run(workingDir, commandName, args);
|
|
||||||
if (!success) {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
const ansiGreen = 32;
|
|
||||||
const ansiRed = 31;
|
|
||||||
const ansiMagenta = 35;
|
|
||||||
|
|
||||||
Future<bool> run(
|
|
||||||
String workingDir,
|
|
||||||
String commandName,
|
|
||||||
List<String> args,
|
|
||||||
) async {
|
|
||||||
var commandDescription = '`${([commandName, ...args]).join(' ')}`';
|
|
||||||
|
|
||||||
logWrapped(ansiMagenta, ' Running $commandDescription');
|
|
||||||
|
|
||||||
var proc = await Process.start(
|
|
||||||
commandName,
|
|
||||||
args,
|
|
||||||
workingDirectory: '${Directory.current.path}/$workingDir',
|
|
||||||
mode: ProcessStartMode.inheritStdio,
|
|
||||||
);
|
|
||||||
|
|
||||||
var exitCode = await proc.exitCode;
|
|
||||||
|
|
||||||
if (exitCode != 0) {
|
|
||||||
logWrapped(
|
|
||||||
ansiRed,
|
|
||||||
' Failed! ($exitCode) – $workingDir – $commandDescription',
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
logWrapped(ansiGreen, ' Success! – $workingDir – $commandDescription');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void logWrapped(int code, String message) {
|
|
||||||
print('\x1B[${code}m$message\x1B[0m');
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<String> listPackageDirs(Directory dir) sync* {
|
|
||||||
if (File('${dir.path}/pubspec.yaml').existsSync()) {
|
|
||||||
yield dir.path;
|
|
||||||
} else {
|
|
||||||
for (var subDir in dir
|
|
||||||
.listSync(followLinks: true)
|
|
||||||
.whereType<Directory>()
|
|
||||||
.where((d) => !Uri.file(d.path).pathSegments.last.startsWith('.'))) {
|
|
||||||
yield* listPackageDirs(subDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
Future<void> main() async {
|
|
||||||
await fixBaseTags();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Changes each sample's `<base href="/">` tag in index.html to
|
|
||||||
/// `<base href="/samples/web/<SAMPLE_DIR_NAME>/">`
|
|
||||||
///
|
|
||||||
/// For example, after building using `build_ci.dart,
|
|
||||||
/// `../samples_index/public/web/navigation_and_routing/index.html` should
|
|
||||||
/// contain `<base href="/samples/web/navigation_and_routing/">`
|
|
||||||
Future<void> fixBaseTags() async {
|
|
||||||
print('currentDir = ${Directory.current.path}');
|
|
||||||
var builtSamplesDir = Directory(
|
|
||||||
p.joinAll([
|
|
||||||
// Parent directory
|
|
||||||
...p.split(Directory.current.path),
|
|
||||||
// path to built samples
|
|
||||||
...p.split('samples_index/public/web'),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
if (!await builtSamplesDir.exists()) {
|
|
||||||
print('${builtSamplesDir.path} does not exist.');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await for (var builtSample in builtSamplesDir.list()) {
|
|
||||||
if (builtSample is Directory) {
|
|
||||||
var index = File(p.join(builtSample.path, 'index.html'));
|
|
||||||
if (!await index.exists()) {
|
|
||||||
throw ('no index.html file found in ${builtSample.path}');
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleDirName = p.split(builtSample.path).last;
|
|
||||||
|
|
||||||
if (await index.exists()) {
|
|
||||||
final regex = RegExp('<base href="(.*)">');
|
|
||||||
var contents = await index.readAsString();
|
|
||||||
if (!contents.contains(regex)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var newContents = contents.replaceFirst(
|
|
||||||
regex,
|
|
||||||
'<base href="/samples/web/$sampleDirName/">',
|
|
||||||
);
|
|
||||||
await index.writeAsString(newContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since all of the web examples use the hosted canvaskit bits
|
|
||||||
// There is no need to deploy these
|
|
||||||
final canvasKitDirectory = Directory(
|
|
||||||
p.join(builtSample.path, 'canvaskit'),
|
|
||||||
);
|
|
||||||
if (canvasKitDirectory.existsSync()) {
|
|
||||||
canvasKitDirectory.deleteSync(recursive: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
// Called by https://pub.dartlang.org/packages/peanut to generate example pages
|
|
||||||
// for hosting.
|
|
||||||
//
|
|
||||||
// Requires at least v3.2.0 of `package:peanut`
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
import 'common.dart';
|
|
||||||
|
|
||||||
void main(List<String> args) async {
|
|
||||||
final buildDir = args[0];
|
|
||||||
final fileMap =
|
|
||||||
(jsonDecode(args[1]) as Map<String, dynamic>).cast<String, String>();
|
|
||||||
|
|
||||||
if (fileMap.length < 2) {
|
|
||||||
throw StateError('We are assuming there is more than one sample!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is USUALLY the case – where we have more than one demo
|
|
||||||
for (var exampleDir in fileMap.values) {
|
|
||||||
for (var htmlFile in Directory(p.join(buildDir, exampleDir))
|
|
||||||
.listSync()
|
|
||||||
.whereType<File>()
|
|
||||||
.where((f) => p.extension(f.path) == '.html')) {
|
|
||||||
_updateHtml(htmlFile, buildDir, exampleDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move each sample into a subdirectory, 'web'
|
|
||||||
for (var exampleDir in fileMap.values) {
|
|
||||||
var oldDirectory = Directory(p.join(buildDir, exampleDir));
|
|
||||||
Directory(p.join(buildDir, 'web')).createSync();
|
|
||||||
oldDirectory.renameSync(p.join(buildDir, 'web', exampleDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the sample index and copy the files into this directory
|
|
||||||
print('building the sample index...');
|
|
||||||
await run('samples_index', 'flutter', ['pub', 'get']);
|
|
||||||
await run('samples_index', 'flutter', ['pub', 'run', 'grinder', 'deploy']);
|
|
||||||
|
|
||||||
// Copy the contents of the samples_index/public directory to the build
|
|
||||||
// directory
|
|
||||||
logWrapped(ansiMagenta, ' Copying samples_index/public to build directory');
|
|
||||||
var contents = Directory(p.join('samples_index', 'public')).listSync();
|
|
||||||
for (var entity in contents) {
|
|
||||||
var newPath = p.join(buildDir, p.basename(entity.path));
|
|
||||||
entity.renameSync(newPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateHtml(File htmlFile, String buildDir, String exampleDir) {
|
|
||||||
final content = htmlFile.readAsStringSync();
|
|
||||||
|
|
||||||
final filePath = p.relative(htmlFile.path, from: buildDir);
|
|
||||||
|
|
||||||
if (!content.contains(_standardMeta)) {
|
|
||||||
print('!!! missing standard meta! - $filePath');
|
|
||||||
}
|
|
||||||
|
|
||||||
final newContent = content
|
|
||||||
.replaceFirst('<head>', '<head>\n$_analytics')
|
|
||||||
.replaceFirst(
|
|
||||||
_emptyTitle,
|
|
||||||
'<title>${_prettyName(exampleDir)} - Flutter web sample</title>',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newContent == content) {
|
|
||||||
print('!!! Did not replace contents in $filePath');
|
|
||||||
} else {
|
|
||||||
print('Replaced contents in $filePath');
|
|
||||||
htmlFile.writeAsStringSync(newContent, flush: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final _underscoreOrSlash = RegExp('_|/');
|
|
||||||
|
|
||||||
String _prettyName(String input) => input
|
|
||||||
.split(_underscoreOrSlash)
|
|
||||||
.where((e) => e.isNotEmpty)
|
|
||||||
.map((e) {
|
|
||||||
return e.substring(0, 1).toUpperCase() + e.substring(1);
|
|
||||||
})
|
|
||||||
.join(' ');
|
|
||||||
|
|
||||||
// flutter.github.io
|
|
||||||
const _analyticsId = 'UA-67589403-8';
|
|
||||||
|
|
||||||
const _analytics = '''
|
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=$_analyticsId"></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', '$_analyticsId');
|
|
||||||
</script>''';
|
|
||||||
|
|
||||||
const _emptyTitle = '<title></title>';
|
|
||||||
|
|
||||||
const _standardMeta = '''
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
$_emptyTitle''';
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
name: tool
|
|
||||||
publish_to: none
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ^3.7.0-0
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
markdown: ^7.0.0
|
|
||||||
path: ^1.8.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
lints: ^5.0.0
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
import 'common.dart';
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
final packageDirs =
|
|
||||||
listPackageDirs(Directory.current)
|
|
||||||
.map((path) => p.relative(path, from: Directory.current.path))
|
|
||||||
.where((path) => !p.dirname(path).startsWith('_'))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
print('Package dirs:\n${packageDirs.map((path) => ' $path').join('\n')}');
|
|
||||||
|
|
||||||
final results = <bool>[];
|
|
||||||
for (var i = 0; i < packageDirs.length; i++) {
|
|
||||||
final dir = packageDirs[i];
|
|
||||||
logWrapped(ansiMagenta, '\n$dir (${i + 1} of ${packageDirs.length})');
|
|
||||||
|
|
||||||
final upgradeResult = await run(dir, 'flutter', [
|
|
||||||
'pub',
|
|
||||||
'pub',
|
|
||||||
'upgrade',
|
|
||||||
'--no-precompile',
|
|
||||||
]);
|
|
||||||
|
|
||||||
results.add(upgradeResult);
|
|
||||||
if (!upgradeResult) {
|
|
||||||
// skipping analyze when `pub upgrade` fails.
|
|
||||||
results.add(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.add(
|
|
||||||
await run(dir, 'dart', [
|
|
||||||
'analyze',
|
|
||||||
'--fatal-infos',
|
|
||||||
'--fatal-warnings',
|
|
||||||
'.',
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
_printStatus(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.any((v) => !v)) {
|
|
||||||
exitCode = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _printStatus(List<bool> results) {
|
|
||||||
var successCount = results.where((t) => t).length;
|
|
||||||
var success = (successCount == results.length);
|
|
||||||
var pct = 100 * successCount / results.length;
|
|
||||||
|
|
||||||
logWrapped(
|
|
||||||
success ? ansiGreen : ansiRed,
|
|
||||||
'$successCount of ${results.length} (${pct.toStringAsFixed(2)}%)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../animations
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../form_app
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../game_template
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../material_3_demo
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../navigation_and_routing
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Configuration for https://pub.dartlang.org/packages/peanut
|
|
||||||
|
|
||||||
directories:
|
|
||||||
- animations/web
|
|
||||||
- provider_shopper/web
|
|
||||||
- charts/web
|
|
||||||
- filipino_cuisine/web
|
|
||||||
- github_dataviz/web
|
|
||||||
- particle_background/web
|
|
||||||
- slide_puzzle/web
|
|
||||||
- form_app/web
|
|
||||||
- web_dashboard/web
|
|
||||||
- place_tracker/web
|
|
||||||
|
|
||||||
post-build-dart-script: _tool/peanut_post_build.dart
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../place_tracker
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../provider_shopper
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Sample Index and Web Demos
|
|
||||||
|
|
||||||
This directory contains the index hosted at [flutter.github.io/samples][samples]
|
|
||||||
and web demos hosted with it.
|
|
||||||
|
|
||||||
## See the demos in action
|
|
||||||
|
|
||||||
Compiled versions of the samples are hosted at
|
|
||||||
https://flutter.github.io/samples/#?platform=web.
|
|
||||||
|
|
||||||
## Building samples code
|
|
||||||
|
|
||||||
Run the demo using the `chrome` device type:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ cd charts
|
|
||||||
$ flutter packages get
|
|
||||||
$ flutter run -d chrome
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see a message printing the URL to access: `http://localhost:8080`
|
|
||||||
|
|
||||||
## Deploying to GitHub Pages
|
|
||||||
|
|
||||||
This project uses a GitHub action to deploy update the `gh-pages` branch. To
|
|
||||||
do this manually, you can also use `package:peanut`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ flutter pub global activate peanut
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify `pub get` has been run on each demo:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ dart run _tool/verify_packages.dart
|
|
||||||
```
|
|
||||||
|
|
||||||
Build all demos, along with the sample index:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ flutter pub global run peanut
|
|
||||||
```
|
|
||||||
|
|
||||||
Deploy to GitHub Pages:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ git push origin gh-pages:gh-pages
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building the sample index
|
|
||||||
|
|
||||||
See sample_index/README.md for details
|
|
||||||
|
|
||||||
[web]: https://flutter.dev/web
|
|
||||||
[samples]: https://flutter.github.io/samples/
|
|
||||||
[peanut]: https://github.com/kevmoo/peanut.dart
|
|
||||||
|
|
||||||
19
web/samples_index/.gitignore
vendored
@@ -1,19 +0,0 @@
|
|||||||
# firebase public directory
|
|
||||||
public/
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.dart_tool/
|
|
||||||
# Remove the following pattern if you wish to check in your lock file
|
|
||||||
pubspec.lock
|
|
||||||
|
|
||||||
# Conventional directory for build outputs
|
|
||||||
build/
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
doc/api/
|
|
||||||
|
|
||||||
# All HTML files are generated by `grind generate`
|
|
||||||
web/*.html
|
|
||||||
|
|
||||||
# Any thumbnails should be ignored
|
|
||||||
web/images/**/*_thumb.png
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Flutter samples index generator
|
|
||||||
|
|
||||||
This tool is used to generate the visual
|
|
||||||
[samples index for Flutter samples](https://flutter.github.io/samples/).
|
|
||||||
|
|
||||||
## Generating the index
|
|
||||||
|
|
||||||
We use [grinder](https://pub.dev/packages/grinder) to run the build tasks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dart pub get
|
|
||||||
$ dart run grinder generate
|
|
||||||
```
|
|
||||||
|
|
||||||
This will generate the index into `./web`
|
|
||||||
|
|
||||||
## Serving the index locally
|
|
||||||
|
|
||||||
If you want to serve the index locally, you can use
|
|
||||||
[webdev](https://pub.dev/packages/webdev):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dart pub global activate grinder
|
|
||||||
$ webdev serve
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publishing the index
|
|
||||||
|
|
||||||
You can build the complete index into a publishable directory using Grinder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dart run grinder build-release
|
|
||||||
```
|
|
||||||
|
|
||||||
This outputs the completely built index to `./public`.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
include: package:lints/recommended.yaml
|
|
||||||
|
|
||||||
analyzer:
|
|
||||||
exclude:
|
|
||||||
- lib/src/data.g.dart
|
|
||||||
language:
|
|
||||||
strict-casts: true
|
|
||||||
strict-inference: true
|
|
||||||
strict-raw-types: true
|
|
||||||
|
|
||||||
linter:
|
|
||||||
rules:
|
|
||||||
avoid_types_on_closure_parameters: true
|
|
||||||
avoid_void_async: true
|
|
||||||
cancel_subscriptions: true
|
|
||||||
close_sinks: true
|
|
||||||
directives_ordering: true
|
|
||||||
package_prefixed_library_names: true
|
|
||||||
prefer_final_in_for_each: true
|
|
||||||
prefer_single_quotes: true
|
|
||||||
test_types_in_equals: true
|
|
||||||
throw_in_finally: true
|
|
||||||
unawaited_futures: true
|
|
||||||
unnecessary_statements: true
|
|
||||||
use_enums: true
|
|
||||||
use_super_parameters: true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
export 'src/search.dart';
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:checked_yaml/checked_yaml.dart';
|
|
||||||
|
|
||||||
import 'src/data.dart';
|
|
||||||
|
|
||||||
export 'src/data.dart';
|
|
||||||
|
|
||||||
Future<List<Sample>> getSamples() async {
|
|
||||||
var yamlFile = File('lib/src/samples.yaml');
|
|
||||||
var contents = await yamlFile.readAsString();
|
|
||||||
var index = checkedYamlDecode(
|
|
||||||
contents, (m) => m != null ? Index.fromJson(m) : null,
|
|
||||||
sourceUrl: yamlFile.uri);
|
|
||||||
if (index == null) throw ('unable to get load from ${yamlFile.uri}');
|
|
||||||
return index.samples;
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
// TODO(kevmoo): https://github.com/flutter/samples/issues/2582
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
import 'dart:html';
|
|
||||||
|
|
||||||
class Carousel {
|
|
||||||
final bool withArrowKeyControl;
|
|
||||||
|
|
||||||
final Element container = querySelector('.slider-container')!;
|
|
||||||
final List<Element> slides = querySelectorAll('.slider-single');
|
|
||||||
|
|
||||||
late int currentSlideIndex;
|
|
||||||
late final int lastSlideIndex;
|
|
||||||
|
|
||||||
late Element prevSlide, currentSlide, nextSlide;
|
|
||||||
|
|
||||||
late num x0;
|
|
||||||
bool touched = false;
|
|
||||||
|
|
||||||
Carousel.init({this.withArrowKeyControl = false}) {
|
|
||||||
lastSlideIndex = slides.length - 1;
|
|
||||||
currentSlideIndex = -1;
|
|
||||||
|
|
||||||
// Remove container empty space when no images are available
|
|
||||||
if (lastSlideIndex == -1) {
|
|
||||||
container.classes.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip carousel decoration when only one image is available
|
|
||||||
if (lastSlideIndex == 0) {
|
|
||||||
currentSlide = slides[currentSlideIndex + 1];
|
|
||||||
currentSlide.classes.add('active');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_hideSlides();
|
|
||||||
_initBullets();
|
|
||||||
_initArrows();
|
|
||||||
_initGestureListener();
|
|
||||||
|
|
||||||
if (withArrowKeyControl) {
|
|
||||||
_initArrowKeyControl();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the first slide after init
|
|
||||||
// This is responsible for creating a smooth animation
|
|
||||||
Future<void>.delayed(const Duration(milliseconds: 500))
|
|
||||||
.then((value) => _slideRight());
|
|
||||||
}
|
|
||||||
|
|
||||||
void _hideSlides() {
|
|
||||||
for (final s in slides) {
|
|
||||||
s.classes.add('next-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initBullets() {
|
|
||||||
final bulletContainer = DivElement();
|
|
||||||
bulletContainer.classes.add('bullet-container');
|
|
||||||
|
|
||||||
for (var i = 0; i < slides.length; i++) {
|
|
||||||
final bullet = DivElement();
|
|
||||||
bullet.classes.add('bullet');
|
|
||||||
bullet.id = 'bullet-index-$i';
|
|
||||||
bullet.onClick.listen((e) => _goToIndexSlide(i));
|
|
||||||
bulletContainer.append(bullet);
|
|
||||||
}
|
|
||||||
|
|
||||||
container.append(bulletContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initArrows() {
|
|
||||||
final prevArrow = AnchorElement();
|
|
||||||
final iPrev = DivElement();
|
|
||||||
iPrev.classes.addAll(['fa', 'fa-chevron-left', 'fa-lg']);
|
|
||||||
prevArrow.classes.add('slider-left');
|
|
||||||
prevArrow.append(iPrev);
|
|
||||||
prevArrow.onClick.listen((e) => _slideLeft());
|
|
||||||
|
|
||||||
final nextArrow = AnchorElement();
|
|
||||||
final iNext = DivElement();
|
|
||||||
iNext.classes.addAll(['fa', 'fa-chevron-right', 'fa-lg']);
|
|
||||||
nextArrow.classes.add('slider-right');
|
|
||||||
nextArrow.append(iNext);
|
|
||||||
nextArrow.onClick.listen((e) => _slideRight());
|
|
||||||
|
|
||||||
container.append(prevArrow);
|
|
||||||
container.append(nextArrow);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _touchStartListener(TouchEvent e) {
|
|
||||||
x0 = e.changedTouches!.first.client.x;
|
|
||||||
touched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _touchEndListener(TouchEvent e) {
|
|
||||||
if (touched) {
|
|
||||||
int dx = (e.changedTouches!.first.client.x - x0) as int;
|
|
||||||
|
|
||||||
// dx==0 case is ignored
|
|
||||||
if (dx > 0 && currentSlideIndex > 0) {
|
|
||||||
_slideLeft();
|
|
||||||
} else if (dx < 0 && currentSlideIndex < lastSlideIndex) {
|
|
||||||
_slideRight();
|
|
||||||
}
|
|
||||||
touched = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initGestureListener() {
|
|
||||||
container.onTouchStart.listen(_touchStartListener);
|
|
||||||
container.onTouchEnd.listen(_touchEndListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateBullets() {
|
|
||||||
final bullets =
|
|
||||||
querySelector('.bullet-container')!.querySelectorAll('.bullet');
|
|
||||||
for (var i = 0; i < bullets.length; i++) {
|
|
||||||
bullets[i].classes.remove('active');
|
|
||||||
if (i == currentSlideIndex) {
|
|
||||||
bullets[i].classes.add('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_checkRepeat();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _checkRepeat() {
|
|
||||||
var prevArrow = querySelector('.slider-left') as AnchorElement;
|
|
||||||
var nextArrow = querySelector('.slider-right') as AnchorElement;
|
|
||||||
|
|
||||||
if (currentSlideIndex == slides.length - 1) {
|
|
||||||
slides[0].classes.add('hidden');
|
|
||||||
slides[slides.length - 1].classes.remove('hidden');
|
|
||||||
prevArrow.classes.remove('hidden');
|
|
||||||
nextArrow.classes.add('hidden');
|
|
||||||
} else if (currentSlideIndex == 0) {
|
|
||||||
slides[slides.length - 1].classes.add('hidden');
|
|
||||||
slides[0].classes.remove('hidden');
|
|
||||||
prevArrow.classes.add('hidden');
|
|
||||||
nextArrow.classes.remove('hidden');
|
|
||||||
} else {
|
|
||||||
slides[slides.length - 1].classes.remove('hidden');
|
|
||||||
slides[0].classes.remove('hidden');
|
|
||||||
prevArrow.classes.remove('hidden');
|
|
||||||
nextArrow.classes.remove('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _slideRight() {
|
|
||||||
if (currentSlideIndex < lastSlideIndex) {
|
|
||||||
currentSlideIndex++;
|
|
||||||
} else {
|
|
||||||
currentSlideIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSlideIndex > 0) {
|
|
||||||
prevSlide = slides[currentSlideIndex - 1];
|
|
||||||
} else {
|
|
||||||
prevSlide = slides[lastSlideIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSlide = slides[currentSlideIndex];
|
|
||||||
|
|
||||||
if (currentSlideIndex < lastSlideIndex) {
|
|
||||||
nextSlide = slides[currentSlideIndex + 1];
|
|
||||||
} else {
|
|
||||||
nextSlide = slides[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final e in slides) {
|
|
||||||
_removeSlideClasses([e]);
|
|
||||||
if (e.classes.contains('prev-hidden')) e.classes.add('next-hidden');
|
|
||||||
if (e.classes.contains('prev')) e.classes.add('prev-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeSlideClasses([prevSlide, currentSlide, nextSlide]);
|
|
||||||
|
|
||||||
prevSlide.classes.add('prev');
|
|
||||||
currentSlide.classes.add('active');
|
|
||||||
nextSlide.classes.add('next');
|
|
||||||
|
|
||||||
_updateBullets();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _slideLeft() {
|
|
||||||
if (currentSlideIndex > 0) {
|
|
||||||
currentSlideIndex--;
|
|
||||||
} else {
|
|
||||||
currentSlideIndex = lastSlideIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSlideIndex < lastSlideIndex) {
|
|
||||||
nextSlide = slides[currentSlideIndex + 1];
|
|
||||||
} else {
|
|
||||||
nextSlide = slides[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSlide = slides[currentSlideIndex];
|
|
||||||
|
|
||||||
if (currentSlideIndex > 0) {
|
|
||||||
prevSlide = slides[currentSlideIndex - 1];
|
|
||||||
} else {
|
|
||||||
prevSlide = slides[lastSlideIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final e in slides) {
|
|
||||||
_removeSlideClasses([e]);
|
|
||||||
if (e.classes.contains('next')) e.classes.add('next-hidden');
|
|
||||||
if (e.classes.contains('next-hidden')) e.classes.add('prev-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeSlideClasses([prevSlide, currentSlide, nextSlide]);
|
|
||||||
|
|
||||||
prevSlide.classes.add('prev');
|
|
||||||
currentSlide.classes.add('active');
|
|
||||||
nextSlide.classes.add('next');
|
|
||||||
|
|
||||||
_updateBullets();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _goToIndexSlide(int index) {
|
|
||||||
final sliding =
|
|
||||||
(currentSlideIndex < index) ? () => _slideRight() : () => _slideLeft();
|
|
||||||
while (currentSlideIndex != index) {
|
|
||||||
sliding();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _removeSlideClasses(List<Element> slides) {
|
|
||||||
for (final s in slides) {
|
|
||||||
s.classes
|
|
||||||
.removeAll(['prev-hidden', 'prev', 'active', 'next', 'next-hidden']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initArrowKeyControl() {
|
|
||||||
Element.keyUpEvent.forTarget(document.body).listen((e) {
|
|
||||||
if (e.keyCode == KeyCode.LEFT && currentSlideIndex > 0) {
|
|
||||||
_slideLeft();
|
|
||||||
}
|
|
||||||
if (e.keyCode == KeyCode.RIGHT && currentSlideIndex < lastSlideIndex) {
|
|
||||||
_slideRight();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
/// Defines the data types for this project.
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:samples_index/src/util.dart' as util;
|
|
||||||
|
|
||||||
part 'data.g.dart';
|
|
||||||
|
|
||||||
/// The full list of samples
|
|
||||||
@JsonSerializable(
|
|
||||||
// Use anyMap and checked for more useful YAML parsing errors. See
|
|
||||||
// package:checked_yaml docs for details.
|
|
||||||
anyMap: true,
|
|
||||||
checked: true)
|
|
||||||
class Index {
|
|
||||||
final List<Sample> samples;
|
|
||||||
|
|
||||||
Index(this.samples);
|
|
||||||
|
|
||||||
factory Index.fromJson(Map<dynamic, dynamic> json) => _$IndexFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$IndexToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sample to be displayed in the app.
|
|
||||||
@JsonSerializable(anyMap: true, checked: true)
|
|
||||||
class Sample {
|
|
||||||
/// The name of the sample.
|
|
||||||
final String name;
|
|
||||||
|
|
||||||
/// The author of the sample. Typically "Flutter"
|
|
||||||
final String? author;
|
|
||||||
|
|
||||||
/// Screenshots of the sample. At least 1 screenshot is required.
|
|
||||||
final List<Screenshot> screenshots;
|
|
||||||
|
|
||||||
/// A link to the source code.
|
|
||||||
final String source;
|
|
||||||
|
|
||||||
/// A link to this sample running in the browser.
|
|
||||||
final String? web;
|
|
||||||
|
|
||||||
/// 3-5 sentences describing the sample.
|
|
||||||
final String description;
|
|
||||||
|
|
||||||
/// The difficulty level. Values are either 'beginner', 'intermediate', or
|
|
||||||
/// 'advanced'.
|
|
||||||
final String? difficulty;
|
|
||||||
|
|
||||||
/// List of widgets or Flutter APIs used by the sample. e.g. "AnimatedBuilder"
|
|
||||||
/// or "ChangeNotifier".
|
|
||||||
final List<String> widgets;
|
|
||||||
|
|
||||||
/// List of packages or Flutter libraries used by the sample. third-party
|
|
||||||
/// packages.
|
|
||||||
final List<String> packages;
|
|
||||||
|
|
||||||
/// Arbitrary tags to associate with this sample.
|
|
||||||
final List<String> tags;
|
|
||||||
|
|
||||||
/// Supported platforms. Values are either 'ios', 'android', 'desktop', and
|
|
||||||
/// 'web'
|
|
||||||
final List<String> platforms;
|
|
||||||
|
|
||||||
/// The type of the sample. Supported values are either 'sample' or
|
|
||||||
/// 'demo'.
|
|
||||||
final String type;
|
|
||||||
|
|
||||||
/// The date this sample was created.
|
|
||||||
final DateTime? date;
|
|
||||||
|
|
||||||
/// The Flutter channel this sample runs on. Either 'stable', 'dev' or
|
|
||||||
/// 'master'.
|
|
||||||
final String? channel;
|
|
||||||
|
|
||||||
Sample({
|
|
||||||
required this.name,
|
|
||||||
this.author = 'Flutter',
|
|
||||||
required this.screenshots,
|
|
||||||
required this.source,
|
|
||||||
this.web,
|
|
||||||
required this.description,
|
|
||||||
this.difficulty = 'beginner',
|
|
||||||
this.widgets = const [],
|
|
||||||
this.packages = const [],
|
|
||||||
this.tags = const [],
|
|
||||||
this.platforms = const [],
|
|
||||||
required this.type,
|
|
||||||
this.date,
|
|
||||||
this.channel,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Sample.fromJson(Map<dynamic, dynamic> json) => _$SampleFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$SampleToJson(this);
|
|
||||||
|
|
||||||
String get thumbnail {
|
|
||||||
var screenshotUrl = screenshots.first.url;
|
|
||||||
var prefix = path.dirname(screenshotUrl);
|
|
||||||
var filename = path.basenameWithoutExtension(screenshotUrl);
|
|
||||||
return path.join(prefix, '${filename}_thumb.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
String get searchAttributes {
|
|
||||||
var buf = StringBuffer();
|
|
||||||
buf.write(name.toLowerCase());
|
|
||||||
buf.write(' ');
|
|
||||||
|
|
||||||
for (final tag in tags) {
|
|
||||||
buf.write('tag:${tag.toLowerCase()} ');
|
|
||||||
|
|
||||||
// Allow tags to be searched without the tag: prefix
|
|
||||||
buf.write('${tag.toLowerCase()} ');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final platform in platforms) {
|
|
||||||
buf.write('platform:$platform ');
|
|
||||||
|
|
||||||
// Allow platforms to be searched without the tag: prefix
|
|
||||||
buf.write('$platform ');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final widget in widgets) {
|
|
||||||
buf.write('widget:$widget ');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final package in packages) {
|
|
||||||
buf.write('package:$package ');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.write('type:$type ');
|
|
||||||
|
|
||||||
return buf.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
String get filename {
|
|
||||||
var nameWithoutChars = name.replaceAll(RegExp(r'[^A-Za-z0-9\-\_\ ]'), '');
|
|
||||||
var nameWithUnderscores = nameWithoutChars.replaceAll(' ', '_');
|
|
||||||
var snake = util.snakeCase(nameWithUnderscores);
|
|
||||||
var s = snake.replaceAll('__', '_');
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
String get shortDescription {
|
|
||||||
if (description.length < 64) {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
return '${description.substring(0, 64)}...';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A screenshot of a sample
|
|
||||||
@JsonSerializable(anyMap: true, checked: true)
|
|
||||||
class Screenshot {
|
|
||||||
final String url;
|
|
||||||
final String alt;
|
|
||||||
|
|
||||||
Screenshot(this.url, this.alt);
|
|
||||||
|
|
||||||
factory Screenshot.fromJson(Map<dynamic, dynamic> json) =>
|
|
||||||
_$ScreenshotFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$ScreenshotToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'data.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
Index _$IndexFromJson(Map json) => $checkedCreate(
|
|
||||||
'Index',
|
|
||||||
json,
|
|
||||||
($checkedConvert) {
|
|
||||||
final val = Index(
|
|
||||||
$checkedConvert(
|
|
||||||
'samples',
|
|
||||||
(v) => (v as List<dynamic>)
|
|
||||||
.map((e) => Sample.fromJson(e as Map))
|
|
||||||
.toList()),
|
|
||||||
);
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$IndexToJson(Index instance) => <String, dynamic>{
|
|
||||||
'samples': instance.samples,
|
|
||||||
};
|
|
||||||
|
|
||||||
Sample _$SampleFromJson(Map json) => $checkedCreate(
|
|
||||||
'Sample',
|
|
||||||
json,
|
|
||||||
($checkedConvert) {
|
|
||||||
final val = Sample(
|
|
||||||
name: $checkedConvert('name', (v) => v as String),
|
|
||||||
author: $checkedConvert('author', (v) => v as String? ?? 'Flutter'),
|
|
||||||
screenshots: $checkedConvert(
|
|
||||||
'screenshots',
|
|
||||||
(v) => (v as List<dynamic>)
|
|
||||||
.map((e) => Screenshot.fromJson(e as Map))
|
|
||||||
.toList()),
|
|
||||||
source: $checkedConvert('source', (v) => v as String),
|
|
||||||
web: $checkedConvert('web', (v) => v as String?),
|
|
||||||
description: $checkedConvert('description', (v) => v as String),
|
|
||||||
difficulty:
|
|
||||||
$checkedConvert('difficulty', (v) => v as String? ?? 'beginner'),
|
|
||||||
widgets: $checkedConvert(
|
|
||||||
'widgets',
|
|
||||||
(v) =>
|
|
||||||
(v as List<dynamic>?)?.map((e) => e as String).toList() ??
|
|
||||||
const []),
|
|
||||||
packages: $checkedConvert(
|
|
||||||
'packages',
|
|
||||||
(v) =>
|
|
||||||
(v as List<dynamic>?)?.map((e) => e as String).toList() ??
|
|
||||||
const []),
|
|
||||||
tags: $checkedConvert(
|
|
||||||
'tags',
|
|
||||||
(v) =>
|
|
||||||
(v as List<dynamic>?)?.map((e) => e as String).toList() ??
|
|
||||||
const []),
|
|
||||||
platforms: $checkedConvert(
|
|
||||||
'platforms',
|
|
||||||
(v) =>
|
|
||||||
(v as List<dynamic>?)?.map((e) => e as String).toList() ??
|
|
||||||
const []),
|
|
||||||
type: $checkedConvert('type', (v) => v as String),
|
|
||||||
date: $checkedConvert(
|
|
||||||
'date', (v) => v == null ? null : DateTime.parse(v as String)),
|
|
||||||
channel: $checkedConvert('channel', (v) => v as String?),
|
|
||||||
);
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SampleToJson(Sample instance) => <String, dynamic>{
|
|
||||||
'name': instance.name,
|
|
||||||
'author': instance.author,
|
|
||||||
'screenshots': instance.screenshots,
|
|
||||||
'source': instance.source,
|
|
||||||
'web': instance.web,
|
|
||||||
'description': instance.description,
|
|
||||||
'difficulty': instance.difficulty,
|
|
||||||
'widgets': instance.widgets,
|
|
||||||
'packages': instance.packages,
|
|
||||||
'tags': instance.tags,
|
|
||||||
'platforms': instance.platforms,
|
|
||||||
'type': instance.type,
|
|
||||||
'date': instance.date?.toIso8601String(),
|
|
||||||
'channel': instance.channel,
|
|
||||||
};
|
|
||||||
|
|
||||||
Screenshot _$ScreenshotFromJson(Map json) => $checkedCreate(
|
|
||||||
'Screenshot',
|
|
||||||
json,
|
|
||||||
($checkedConvert) {
|
|
||||||
final val = Screenshot(
|
|
||||||
$checkedConvert('url', (v) => v as String),
|
|
||||||
$checkedConvert('alt', (v) => v as String),
|
|
||||||
);
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$ScreenshotToJson(Screenshot instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'url': instance.url,
|
|
||||||
'alt': instance.alt,
|
|
||||||
};
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
samples:
|
|
||||||
- name: Material 3
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/material_3_components.png
|
|
||||||
alt: Components tab of the Material 3 demo
|
|
||||||
- url: images/material_3_color.png
|
|
||||||
alt: Colors tab of the Material 3 demo
|
|
||||||
- url: images/material_3_typography.png
|
|
||||||
alt: Typography tab of the Material 3 demo
|
|
||||||
- url: images/material_3_elevation.png
|
|
||||||
alt: Elevation tab of the Material 3 demo
|
|
||||||
- url: images/material_3_green.png
|
|
||||||
alt: Elevation tab of the Material 3 demo with seed color of green
|
|
||||||
source: https://github.com/flutter/samples/tree/main/material_3_demo
|
|
||||||
description: >
|
|
||||||
Showcases Material 3 features in the Flutter Material library.
|
|
||||||
These features include updated components, typography, color system and elevation support.
|
|
||||||
difficulty: beginner
|
|
||||||
widgets:
|
|
||||||
- Theme
|
|
||||||
- TextButton
|
|
||||||
- ElevatedButton
|
|
||||||
- OutlinedButton
|
|
||||||
- Text
|
|
||||||
- Card
|
|
||||||
- AppBar
|
|
||||||
packages: [ ]
|
|
||||||
tags: [ "material", "design", "gallery" ]
|
|
||||||
platforms: [ "ios", "android", "web", "windows", "macos", "linux" ]
|
|
||||||
type: demo
|
|
||||||
web: web/material_3_demo
|
|
||||||
|
|
||||||
- name: Rich Text Editor
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/simple_editor_active.png
|
|
||||||
alt: Advanced text editing with activity
|
|
||||||
- url: images/simple_editor_initial.png
|
|
||||||
alt: Advanced text editing in initial state
|
|
||||||
source: https://github.com/flutter/samples/tree/main/simplistic_editor
|
|
||||||
description: >
|
|
||||||
This is a fancy text editor sample which shows how to consume fine-grain
|
|
||||||
text editing and selection details from the framework's TextEditingDeltas
|
|
||||||
APIs.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- TextInput
|
|
||||||
packages: []
|
|
||||||
tags: ["demo", "text"]
|
|
||||||
platforms: ["ios", "android", "web", "windows", "macos", "linux"]
|
|
||||||
type: demo
|
|
||||||
web: web/simplistic_editor
|
|
||||||
|
|
||||||
- name: Web Embedding
|
|
||||||
author: Flutter and Angular
|
|
||||||
screenshots:
|
|
||||||
- url: images/web_embedding1.png
|
|
||||||
alt: A Flutter app embedded in an Angular app
|
|
||||||
- url: images/web_embedding2.png
|
|
||||||
alt: A Flutter app embedded in an Angular app
|
|
||||||
source: https://github.com/flutter/samples/tree/main/web_embedding
|
|
||||||
description: >
|
|
||||||
An example app showing how to embed Flutter in a web application using Angular
|
|
||||||
difficulty: advanced
|
|
||||||
widgets: []
|
|
||||||
packages: []
|
|
||||||
platforms: ['web']
|
|
||||||
tags: ['demo', 'web', 'add-to-app', 'embedding']
|
|
||||||
web: https://flutter-angular.web.app/
|
|
||||||
type: demo
|
|
||||||
|
|
||||||
- name: Add to App
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/add_to_app1.png
|
|
||||||
alt: Add_to_app screenshot
|
|
||||||
- url: images/add_to_app2.png
|
|
||||||
alt: Add_to_app screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/add_to_app
|
|
||||||
description: >
|
|
||||||
Android and iOS projects that each import a standalone Flutter module.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- WidgetsFlutterBinding
|
|
||||||
- MethodChannel
|
|
||||||
packages:
|
|
||||||
- flutter/material
|
|
||||||
- flutter/services
|
|
||||||
- provider
|
|
||||||
tags: ['advanced', 'sample', 'add-to-app', 'android', 'ios', 'native', 'embedding']
|
|
||||||
platforms: ['ios', 'android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Code Sharing
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/code_sharing.jpg
|
|
||||||
alt: Counter app communicating with server
|
|
||||||
source: https://github.com/flutter/samples/tree/main/code_sharing
|
|
||||||
description: >
|
|
||||||
Demonstrates simple way to share business logic between a Flutter app and
|
|
||||||
a server running Dart.
|
|
||||||
difficulty: intermediate
|
|
||||||
packages:
|
|
||||||
- freezed
|
|
||||||
- shelf
|
|
||||||
tags: ['intermediate', 'sample', 'code-sharing', 'dart', 'server']
|
|
||||||
platforms: ['android', 'ios', 'linux', 'macos', 'web', 'windows']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Animations
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/animations1.png
|
|
||||||
alt: Animations sample screenshot
|
|
||||||
- url: images/animations2.png
|
|
||||||
alt: Animations sample screenshot
|
|
||||||
- url: images/animations3.png
|
|
||||||
alt: Animations sample screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/animations
|
|
||||||
description: >
|
|
||||||
Sample apps that showcasing Flutter's animation features.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- AnimatedContainer
|
|
||||||
- PageRouteBuilder
|
|
||||||
- AnimationController
|
|
||||||
- SingleTickerProviderStateMixin
|
|
||||||
- Tween
|
|
||||||
- AnimatedBuilder
|
|
||||||
- TweenSequence
|
|
||||||
- TweenSequenceItem
|
|
||||||
packages:
|
|
||||||
- flutter/material
|
|
||||||
tags: ['intermediate', 'sample', 'animation']
|
|
||||||
platforms: ['ios', 'android', 'web']
|
|
||||||
type: sample
|
|
||||||
web: web/animations
|
|
||||||
|
|
||||||
- name: Flutter Maps Firestore
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/flutter_maps_firestore1.png
|
|
||||||
alt: Flutter maps firestore screenshot
|
|
||||||
- url: images/flutter_maps_firestore2.png
|
|
||||||
alt: Flutter maps firestore screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/flutter_maps_firestore
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows the end product of the Cloud Next '19 talk
|
|
||||||
Build Mobile Apps With Flutter and Google Maps.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- GoogleMap
|
|
||||||
packages:
|
|
||||||
- flutter/material
|
|
||||||
- cloud_firestore
|
|
||||||
- google_maps_flutter
|
|
||||||
- google_maps_webservice
|
|
||||||
tags: ['intermediate', 'sample', 'firebase', 'maps']
|
|
||||||
platforms: ['ios', 'android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Isolate Example
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/isolate1.png
|
|
||||||
alt: Isolate example screenshot
|
|
||||||
- url: images/isolate2.png
|
|
||||||
alt: Isolate example screenshot
|
|
||||||
- url: images/isolate3.png
|
|
||||||
alt: Isolate example screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/isolate_example
|
|
||||||
description: >
|
|
||||||
A sample application that demonstrate best practices when using
|
|
||||||
isolates.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- FutureBuilder
|
|
||||||
- AnimationController
|
|
||||||
packages:
|
|
||||||
- dart:isolate
|
|
||||||
- dart:math
|
|
||||||
tags: ['intermediate', 'sample', 'isolates', 'concurrency']
|
|
||||||
platforms: ['ios', 'android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Place Tracker
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/place_tracker1.png
|
|
||||||
alt: Place Tracker screenshot
|
|
||||||
- url: images/place_tracker2.png
|
|
||||||
alt: Place Tracker screenshot
|
|
||||||
- url: images/place_tracker3.png
|
|
||||||
alt: Place Tracker screenshot
|
|
||||||
- url: images/place_tracker4.png
|
|
||||||
alt: Place Tracker screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/place_tracker
|
|
||||||
description: >
|
|
||||||
A sample place tracking app that uses the google_maps_flutter plugin. Keep
|
|
||||||
track of your favorite places, places you've visited, and places you want
|
|
||||||
to go. View details about these places, show them on a map, and get
|
|
||||||
directions to them.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- GoogleMap
|
|
||||||
packages:
|
|
||||||
- google_maps_flutter
|
|
||||||
tags: ['intermediate', 'sample', 'json', 'serialization']
|
|
||||||
platforms: ['android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Platform Design
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/platform_design1.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
- url: images/platform_design2.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
- url: images/platform_design3.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
- url: images/platform_design4.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
- url: images/platform_design5.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
- url: images/platform_design6.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
- url: images/platform_design7.png
|
|
||||||
alt: Platform Design screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/platform_design
|
|
||||||
description: >
|
|
||||||
A Flutter app that maximizes application code reuse while adhering to
|
|
||||||
different design patterns on Android and iOS
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- TargetPlatform
|
|
||||||
packages:
|
|
||||||
- flutter/material
|
|
||||||
- flutter/cupertino
|
|
||||||
tags: ['advanced', 'sample', 'ios']
|
|
||||||
platforms: ['ios', 'android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Platform View Swift
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/platform_view_swift1.png
|
|
||||||
alt: Platform View Swift screenshot
|
|
||||||
- url: images/platform_view_swift2.png
|
|
||||||
alt: Platform View Swift screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/platform_view_swift
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that combines a native iOS UIViewController with a
|
|
||||||
full-screen Flutter view.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- MethodChannel
|
|
||||||
packages:
|
|
||||||
- flutter/material
|
|
||||||
- flutter/services
|
|
||||||
tags: ['advanced', 'sample', 'ios']
|
|
||||||
platforms: ['ios']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Infinite List
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/infinite_list.png
|
|
||||||
alt: Infinite List screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/infinite_list
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows an implementation of the "infinite list" UX pattern.
|
|
||||||
That is, a list is shown to the user as if it was continuous although it is internally
|
|
||||||
paginated. This is a common feature of mobile apps, from shopping catalogs
|
|
||||||
through search engines to social media clients.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- Selector
|
|
||||||
- AppBar
|
|
||||||
- ListTile
|
|
||||||
- ListView
|
|
||||||
packages:
|
|
||||||
- provider
|
|
||||||
- meta
|
|
||||||
tags: ['sample', 'material', 'design', 'android', 'ios']
|
|
||||||
platforms: ['ios', 'android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: IOS App Clip
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/ios_app_clip.png
|
|
||||||
alt: IOS App Clip screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/ios_app_clip
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows the demonstrating integration with iOS 14's App Clip,
|
|
||||||
the App Clip target is rendered by Flutter and uses a plugin.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- CupertinoApp
|
|
||||||
- AppBar
|
|
||||||
- FlutterLogo
|
|
||||||
packages:
|
|
||||||
- device_info
|
|
||||||
tags: ['sample', 'Device Info', 'ios']
|
|
||||||
platforms: ['ios']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Testing App
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/testing_app1.png
|
|
||||||
alt: Testing App screenshot
|
|
||||||
- url: images/testing_app2.png
|
|
||||||
alt: Testing App screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/testing_app
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows different types of testing in Flutter.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- AppBar
|
|
||||||
- ListTile
|
|
||||||
- ListView
|
|
||||||
- Snackbar
|
|
||||||
packages:
|
|
||||||
- provider
|
|
||||||
tags: ['sample', 'material', 'android', 'ios']
|
|
||||||
platforms: ['ios', 'android']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Provider Shopper
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/provider_shopper1.png
|
|
||||||
alt: Provider Shopper screenshot
|
|
||||||
- url: images/provider_shopper2.png
|
|
||||||
alt: Provider Shopper screenshot
|
|
||||||
- url: images/provider_shopper3.png
|
|
||||||
alt: Provider Shopper screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/provider_shopper
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows a state management approach using the Provider package.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- Provider
|
|
||||||
- MultiProvider
|
|
||||||
- ChangeNotifier
|
|
||||||
packages:
|
|
||||||
- provider
|
|
||||||
tags: ['intermediate', 'sample', 'provider']
|
|
||||||
platforms: ['ios', 'android', 'web']
|
|
||||||
type: sample
|
|
||||||
web: web/provider_shopper
|
|
||||||
|
|
||||||
- name: Web Dashboard
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/web_dashboard1.png
|
|
||||||
alt: Web Dashboard screenshot
|
|
||||||
- url: images/web_dashboard2.png
|
|
||||||
alt: Web Dashboard screenshot
|
|
||||||
- url: images/web_dashboard3.png
|
|
||||||
alt: Web Dashboard screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/experimental/web_dashboard
|
|
||||||
description: >
|
|
||||||
A dashboard app that displays daily entries. Demonstrates AdaptiveScaffold and NavigationRail. Showcases how to
|
|
||||||
use Firebase, but uses a mock backend by default.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- AdaptiveScaffold
|
|
||||||
- NavigationRail
|
|
||||||
- FutureBuilder
|
|
||||||
- StreamBuilder
|
|
||||||
packages:
|
|
||||||
- firebase
|
|
||||||
tags: ['intermediate', 'sample', 'firebase']
|
|
||||||
platforms: ['ios', 'android', 'web']
|
|
||||||
type: sample
|
|
||||||
web: web/web_dashboard
|
|
||||||
|
|
||||||
- name: Form App
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/form_app1.png
|
|
||||||
alt: Form App screenshot
|
|
||||||
- url: images/form_app2.png
|
|
||||||
alt: Form App screenshot
|
|
||||||
- url: images/form_app3.png
|
|
||||||
alt: Form App screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/form_app
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows how to use Forms.
|
|
||||||
difficulty: intermediate
|
|
||||||
widgets:
|
|
||||||
- Form
|
|
||||||
packages: []
|
|
||||||
tags: ['intermediate', 'sample', 'forms']
|
|
||||||
platforms: ['ios', 'android', 'web']
|
|
||||||
type: sample
|
|
||||||
web: web/form_app
|
|
||||||
|
|
||||||
- name: Navigation and Routing
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/navigation_and_routing1.png
|
|
||||||
alt: Navigation and Routing screenshot
|
|
||||||
- url: images/navigation_and_routing2.png
|
|
||||||
alt: Navigation and Routing screenshot
|
|
||||||
- url: images/navigation_and_routing3.png
|
|
||||||
alt: Navigation and Routing screenshot
|
|
||||||
- url: images/navigation_and_routing4.png
|
|
||||||
alt: Navigation and Routing screenshot
|
|
||||||
source: https://github.com/flutter/samples/tree/main/navigation_and_routing
|
|
||||||
description: >
|
|
||||||
A Flutter sample app that shows how to use how to use the Router API to
|
|
||||||
handle common navigation scenarios.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets:
|
|
||||||
- Router
|
|
||||||
packages: []
|
|
||||||
tags: ['advanced', 'sample', 'navigation', 'router']
|
|
||||||
platforms: ['ios', 'android', 'web']
|
|
||||||
type: sample
|
|
||||||
web: web/navigation_and_routing
|
|
||||||
|
|
||||||
- name: Photo Search app
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/desktop_photo_search-fluent_ui.png
|
|
||||||
alt: Desktop Photo Search with FluentUI widgets
|
|
||||||
- url: images/desktop_photo_search-material.png
|
|
||||||
alt: Desktop Photo Search with Material widgets
|
|
||||||
source: https://github.com/flutter/samples/tree/main/desktop_photo_search
|
|
||||||
description: >
|
|
||||||
This is the Photo Search app, built out with two different widget sets,
|
|
||||||
`material` shows the Photo Search app built with Material widgets, and
|
|
||||||
`fluent_ui` shows the Photo Search app built with Fluent UI widgets.
|
|
||||||
difficulty: medium
|
|
||||||
widgets: []
|
|
||||||
packages:
|
|
||||||
- built_collection
|
|
||||||
- built_value
|
|
||||||
- file_selector
|
|
||||||
- fluent_ui
|
|
||||||
- flutter/material
|
|
||||||
- provider
|
|
||||||
- url_launcher
|
|
||||||
tags: ['desktop', 'rest-api']
|
|
||||||
platforms: ['windows', 'macos', 'linux']
|
|
||||||
type: sample
|
|
||||||
|
|
||||||
- name: Slide Puzzle
|
|
||||||
author: Very Good Ventures
|
|
||||||
screenshots:
|
|
||||||
- url: images/slide_puzzle1.png
|
|
||||||
alt: Slide Puzzle screenshot
|
|
||||||
source: https://github.com/VGVentures/slide_puzzle
|
|
||||||
description: >
|
|
||||||
A slide puzzle built for Flutter Challenge.
|
|
||||||
difficulty: advanced
|
|
||||||
widgets: []
|
|
||||||
packages: []
|
|
||||||
platforms: ['web']
|
|
||||||
tags: ['demo', 'game']
|
|
||||||
type: demo
|
|
||||||
|
|
||||||
- name: Game Template
|
|
||||||
author: Flutter
|
|
||||||
screenshots:
|
|
||||||
- url: images/loading_screen.png
|
|
||||||
alt: Loading screen
|
|
||||||
- url: images/level_selector.png
|
|
||||||
alt: Level selection screen
|
|
||||||
source: https://github.com/flutter/samples/tree/main/game_template
|
|
||||||
description: >
|
|
||||||
This is a game template that shows how to build much of the dressing
|
|
||||||
around an actual game. The game itself is very simple - the value in this
|
|
||||||
sample is everything around the game.
|
|
||||||
difficulty: beginner
|
|
||||||
widgets:
|
|
||||||
- GoRouter
|
|
||||||
- AppLifecycleObserver
|
|
||||||
packages:
|
|
||||||
- audioplayers
|
|
||||||
- firebase_crashlytics
|
|
||||||
- games_services
|
|
||||||
- go_router
|
|
||||||
- google_mobile_ads
|
|
||||||
- in_app_purchase
|
|
||||||
- logging
|
|
||||||
- provider
|
|
||||||
- shared_preferences
|
|
||||||
tags: ["games", "firebase", "ads", "crashlytics", "routing"]
|
|
||||||
platforms: ["ios", "android", "web", "windows", "macos", "linux"]
|
|
||||||
type: demo
|
|
||||||
web: web/game_template
|
|
||||||
|
|
||||||
- name: Dice
|
|
||||||
author: Jaime Blasco
|
|
||||||
screenshots:
|
|
||||||
- url: images/dice.png
|
|
||||||
alt: Dice screenshot
|
|
||||||
source: https://github.com/jamesblasco/zflutter/blob/master/zflutter/example/lib/examples/dice/dice.dart
|
|
||||||
description: >
|
|
||||||
A demo of 3d animation using dice
|
|
||||||
difficulty: advanced
|
|
||||||
widgets: []
|
|
||||||
packages: []
|
|
||||||
platforms: ['web']
|
|
||||||
tags: ['demo', 'animation']
|
|
||||||
web: https://z.flutter.gallery/#/dice
|
|
||||||
type: demo
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
bool matchesQuery(String query, String sampleAttributes) {
|
|
||||||
if (query.isEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryWords = query.toLowerCase().split(' ')
|
|
||||||
..removeWhere((s) => s.isEmpty);
|
|
||||||
var attributes = sampleAttributes.toLowerCase().split(' ')
|
|
||||||
..removeWhere((s) => s.isEmpty);
|
|
||||||
|
|
||||||
// Test for type filter
|
|
||||||
// This will check whether a type parameter is present in the
|
|
||||||
// search query, and return false if the self type mismatches
|
|
||||||
// the query type
|
|
||||||
for (final word in queryWords) {
|
|
||||||
if ((word.contains('type:') && !attributes.contains(word)) ||
|
|
||||||
(word.contains('platform:') && !attributes.contains('type:demo'))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for exact matches
|
|
||||||
if (attributes.contains(query)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for exact matches for keywords
|
|
||||||
var matches = 0;
|
|
||||||
for (final word in queryWords) {
|
|
||||||
if (attributes.contains(word)) {
|
|
||||||
matches++;
|
|
||||||
}
|
|
||||||
if (matches == queryWords.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for queries whose keywords are a substring of any attribute
|
|
||||||
// e.g. searching "kitten tag:cats" is a match for a sample with the
|
|
||||||
// attributes "kittens tag:cats"
|
|
||||||
matches = 0;
|
|
||||||
for (final attribute in attributes) {
|
|
||||||
for (final queryWord in queryWords) {
|
|
||||||
if (attribute.startsWith(queryWord)) {
|
|
||||||
matches++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Only return true if each search term was matched
|
|
||||||
if (matches == queryWords.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> parseHash(String hash) =>
|
|
||||||
Uri.parse(hash.substring(hash.indexOf('#') + 1)).queryParameters;
|
|
||||||
|
|
||||||
String formatHash(Map<String, String> parameters) =>
|
|
||||||
Uri().replace(queryParameters: parameters).toString();
|
|
||||||
|
|
||||||
String searchQueryFromParams(Map<String, String> params) {
|
|
||||||
var buf = StringBuffer();
|
|
||||||
if (params.containsKey('search')) {
|
|
||||||
buf.write(params['search']);
|
|
||||||
}
|
|
||||||
if (params.containsKey('type')) {
|
|
||||||
if (buf.isNotEmpty) buf.write(' ');
|
|
||||||
var value = params['type'];
|
|
||||||
if (value != null) buf.write('type:$value');
|
|
||||||
}
|
|
||||||
if (params.containsKey('platform')) {
|
|
||||||
if (buf.isNotEmpty) buf.write(' ');
|
|
||||||
var value = params['platform'];
|
|
||||||
if (value != null) buf.write('platform:$value');
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'data.dart';
|
|
||||||
import 'util.dart' as util;
|
|
||||||
|
|
||||||
String _escapeAttribute(String s) =>
|
|
||||||
const HtmlEscape(HtmlEscapeMode.attribute).convert(s);
|
|
||||||
String _escapeElement(String s) =>
|
|
||||||
const HtmlEscape(HtmlEscapeMode.element).convert(s);
|
|
||||||
|
|
||||||
String description(Sample sample) => '''
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
$_descriptionHeader
|
|
||||||
${_descriptionPage(sample)}
|
|
||||||
$_footer
|
|
||||||
</html>
|
|
||||||
''';
|
|
||||||
|
|
||||||
String index(List<Sample> samples) => '''
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
$_indexHeader
|
|
||||||
${_indexBody(samples)}
|
|
||||||
$_footer
|
|
||||||
</html>
|
|
||||||
''';
|
|
||||||
|
|
||||||
const String _indexHeader = '''
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Flutter samples</title>
|
|
||||||
<link href="styles.css" rel="stylesheet" media="screen">
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Google+Sans|Google+Sans+Display|Roboto:300,400,500&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="packages/mdc_web/material-components-web.min.js"></script>
|
|
||||||
<script defer src="main.dart.js"></script>
|
|
||||||
$_googleAnalytics
|
|
||||||
</head>
|
|
||||||
''';
|
|
||||||
|
|
||||||
const String _descriptionHeader = '''
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Flutter samples</title>
|
|
||||||
<link href="styles.css" rel="stylesheet" media="screen">
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Google+Sans|Google+Sans+Display|Roboto:300,400,500&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="packages/mdc_web/material-components-web.min.js"></script>
|
|
||||||
<script src="https://kit.fontawesome.com/16cc04762e.js"></script>
|
|
||||||
<script defer src="description.dart.js"></script>
|
|
||||||
$_googleAnalytics
|
|
||||||
</head>
|
|
||||||
''';
|
|
||||||
|
|
||||||
const String _navbar = '''
|
|
||||||
<div class="navbar">
|
|
||||||
<a class="leading" href="./">
|
|
||||||
<img src="images/logos/logo_lockup_flutter_horizontal_wht_96.png" />
|
|
||||||
<span class="title">Samples</span>
|
|
||||||
</a>
|
|
||||||
<div class="nav-items">
|
|
||||||
<a href="https://flutter.dev/">Flutter Home</a>
|
|
||||||
<a href="https://api.flutter.dev/">API Docs</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
''';
|
|
||||||
|
|
||||||
String _footer = '''
|
|
||||||
<div class="footer">
|
|
||||||
<span>© Flutter ${DateTime.now().toUtc().year}</span>
|
|
||||||
</div>
|
|
||||||
''';
|
|
||||||
|
|
||||||
String _indexBody(List<Sample> samples) => '''
|
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
${util.indent(_navbar, 4)}
|
|
||||||
<div class="container">
|
|
||||||
<div class="index-header">
|
|
||||||
<h1>All Samples</h1>
|
|
||||||
<p>A curated list of Flutter samples and apps</p>
|
|
||||||
</div>
|
|
||||||
<div class="search-container">
|
|
||||||
<div id="search-bar" class="mdc-text-field mdc-text-field--with-leading-icon mdc-text-field--with-trailing-icon">
|
|
||||||
<i class="material-icons mdc-text-field__icon">search</i>
|
|
||||||
<i id="clear-button" class="material-icons mdc-text-field__icon" role="button" tabindex="0">clear</i>
|
|
||||||
<input class="mdc-text-field__input" id="text-field-hero-input">
|
|
||||||
<div class="mdc-line-ripple"></div>
|
|
||||||
<label for="text-field-hero-input" class="mdc-floating-label">Search</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="filter-menu">
|
|
||||||
<div class="filter-buttons">
|
|
||||||
<div class="mdc-chip-set mdc-chip-set--choice" role="grid">
|
|
||||||
<div class="mdc-chip mdc-chip--selected" role="row">
|
|
||||||
<div class="mdc-chip__ripple"></div>
|
|
||||||
<span role="gridcell">
|
|
||||||
<span role="button" tabindex="0" class="mdc-chip__text">All</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="mdc-chip" role="row">
|
|
||||||
<div class="mdc-chip__ripple"></div>
|
|
||||||
<span role="gridcell">
|
|
||||||
<span role="button" tabindex="-1" class="mdc-chip__text">Sample</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="mdc-chip" role="row">
|
|
||||||
<div class="mdc-chip__ripple"></div>
|
|
||||||
<span role="gridcell">
|
|
||||||
<span role="button" tabindex="-1" class="mdc-chip__text">Web Demos</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="filter-end"></div>
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
${util.indent(_indexCards(samples), 6)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
''';
|
|
||||||
|
|
||||||
String _backgroundImage(String url) =>
|
|
||||||
_escapeAttribute('background-image: url(\'$url\');');
|
|
||||||
String _indexCards(List<Sample> samples) => samples.map(_indexCard).join();
|
|
||||||
String _indexCard(Sample sample) => '''
|
|
||||||
<div class="mdc-card demo-card mdc-elevation--z0" search-attrs="${_escapeAttribute(sample.searchAttributes)}">
|
|
||||||
<div class="mdc-card__primary-action demo-card__primary-action" tabindex="0" href="${sample.filename}.html">
|
|
||||||
<div class="mdc-card__media mdc-card__media--16-9 demo-card__media" style="${_backgroundImage(sample.thumbnail)}"></div>
|
|
||||||
<div class="demo-card__label type-label">${_escapeElement(sample.type)}</div>
|
|
||||||
<div class="demo-card__primary">
|
|
||||||
<h2 class="demo-card__title mdc-typography mdc-typography--headline6">${_escapeElement(sample.name)}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="demo-card__secondary mdc-typography mdc-typography--body2">${sample.shortDescription}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
''';
|
|
||||||
|
|
||||||
String _descriptionPage(Sample sample) => '''
|
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
${util.indent(_navbar, 4)}
|
|
||||||
<div class="container">
|
|
||||||
<div class="description-title-row">
|
|
||||||
<h1>${sample.name}</h1>
|
|
||||||
<div class="type-label type-label-bordered">${sample.type}</div>
|
|
||||||
</div>
|
|
||||||
<p>By ${sample.author}</p>
|
|
||||||
<div class="toolbar">
|
|
||||||
<div class="buttons">
|
|
||||||
${util.indent(_descriptionButtons(sample), 6)}
|
|
||||||
</div>
|
|
||||||
<div class="tags-container">
|
|
||||||
<div class="tags-label">
|
|
||||||
<i class="material-icons">local_offer</i>
|
|
||||||
<span>Tags</span>
|
|
||||||
</div>
|
|
||||||
<div class="tags">
|
|
||||||
${util.indent(_tags(sample), 8)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slider-container">
|
|
||||||
<div class="slider-content">
|
|
||||||
${util.indent(_descriptionScreenshots(sample), 4)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="description">
|
|
||||||
${util.indent(_descriptionText(sample), 4)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
''';
|
|
||||||
|
|
||||||
String _descriptionButtons(Sample sample) {
|
|
||||||
var buf = StringBuffer();
|
|
||||||
var sampleLink = sample.web;
|
|
||||||
if (sampleLink != null && sampleLink.isNotEmpty) {
|
|
||||||
buf.write(
|
|
||||||
'''<button class="mdc-button mdc-button--outlined" onclick="window.location.href = '$sampleLink';"><span class="mdc-button__ripple"></span> Launch App</button>''');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sample.type == 'app' ||
|
|
||||||
sample.type == 'sample' ||
|
|
||||||
sample.type == 'demo') {
|
|
||||||
buf.write(
|
|
||||||
'''<button class="mdc-button mdc-button--outlined" onclick="window.location.href = '${sample.source}';">
|
|
||||||
<div class="mdc-button__ripple"></div>
|
|
||||||
<i class="material-icons mdc-button__icon" aria-hidden="true">code</i>
|
|
||||||
<span class="mdc-button__label">Source Code</span>
|
|
||||||
</button>''');
|
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _tags(Sample sample) {
|
|
||||||
var buf = StringBuffer();
|
|
||||||
for (final tag in sample.tags) {
|
|
||||||
buf.write('<a href="./#?search=tag%3A$tag">$tag</a>\n');
|
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _descriptionScreenshots(Sample sample) {
|
|
||||||
var buf = StringBuffer();
|
|
||||||
for (final screenshot in sample.screenshots) {
|
|
||||||
buf.write(
|
|
||||||
'''<div class="slider-single"><img class="slider-single-image" src="${screenshot.url}" alt="${screenshot.alt}" /></div>\n''');
|
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _descriptionText(Sample sample) {
|
|
||||||
return '<p>${sample.description}</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const String _googleAnalytics = """
|
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-67589403-8"></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', 'UA-67589403-8');
|
|
||||||
</script>""";
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
String indent(String content, int spaces) =>
|
|
||||||
LineSplitter.split(content).join('\n${' ' * spaces}');
|
|
||||||
|
|
||||||
String kebabCase(String input) => _fixCase(input, '-');
|
|
||||||
|
|
||||||
String snakeCase(String input) => _fixCase(input, '_');
|
|
||||||
|
|
||||||
final _upperCase = RegExp('[A-Z]');
|
|
||||||
|
|
||||||
String pascalCase(String input) {
|
|
||||||
if (input.isEmpty) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return input[0].toUpperCase() + input.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _fixCase(String input, String separator) =>
|
|
||||||
input.replaceAllMapped(_upperCase, (match) {
|
|
||||||
var group = match.group(0);
|
|
||||||
if (group == null) return input;
|
|
||||||
var lower = group.toLowerCase();
|
|
||||||
|
|
||||||
if (match.start > 0) {
|
|
||||||
lower = '$separator$lower';
|
|
||||||
}
|
|
||||||
|
|
||||||
return lower;
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
name: samples_index
|
|
||||||
description: A visual index of Flutter samples.
|
|
||||||
homepage: https://github.com/flutter/samples/tree/main/web/samples_index
|
|
||||||
version: 0.0.1
|
|
||||||
publish_to: none
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ^3.5.0
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
checked_yaml: ^2.0.3
|
|
||||||
json_annotation: ^4.8.1
|
|
||||||
mdc_web: ^0.6.0
|
|
||||||
path: ^1.8.3
|
|
||||||
sass_builder: ^2.2.1
|
|
||||||
web: ^1.1.0
|
|
||||||
yaml: ^3.1.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
build: ^2.4.0
|
|
||||||
build_runner: ^2.4.2
|
|
||||||
build_web_compilers: ^4.0.3
|
|
||||||
grinder: ^0.9.4
|
|
||||||
image: ^4.1.3
|
|
||||||
json_serializable: ^6.6.2
|
|
||||||
lints: ^5.0.0
|
|
||||||
test: ^1.24.2
|
|
||||||
|
|
||||||
# package:mdc_web needs to upgrade the version of material-components-web 12.0.0
|
|
||||||
# or above, which includes this fix for the division operator:
|
|
||||||
|
|
||||||
# https://github.com/material-components/material-components-web/pull/7158
|
|
||||||
#
|
|
||||||
# Until then, dart-sass produces a warning that this operator is being removed
|
|
||||||
# in favor of calc().
|
|
||||||
#
|
|
||||||
# See this issue for details:
|
|
||||||
# https://github.com/dart-lang/dart-pad/issues/2388
|
|
||||||
dependency_overrides:
|
|
||||||
sass: ^1.62.0
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:checked_yaml/checked_yaml.dart';
|
|
||||||
import 'package:samples_index/browser.dart';
|
|
||||||
import 'package:samples_index/samples_index.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('YAML', () {
|
|
||||||
test('parsing', () async {
|
|
||||||
var file = File('test/yaml/single.yaml');
|
|
||||||
var contents = await file.readAsString();
|
|
||||||
expect(contents, isNotEmpty);
|
|
||||||
|
|
||||||
var index = checkedYamlDecode(
|
|
||||||
contents, (m) => m != null ? Index.fromJson(m) : null,
|
|
||||||
sourceUrl: file.uri);
|
|
||||||
if (index == null) {
|
|
||||||
throw ('unable to load YAML from $file');
|
|
||||||
}
|
|
||||||
expect(index.samples, isNotEmpty);
|
|
||||||
|
|
||||||
var sample = index.samples.first;
|
|
||||||
expect(sample, isNotNull);
|
|
||||||
expect(sample.name, 'Kittens');
|
|
||||||
expect(sample.screenshots, hasLength(2));
|
|
||||||
expect(sample.source, 'https://github.com/johnpryan/kittens');
|
|
||||||
expect(sample.description, 'A sample kitten app');
|
|
||||||
expect(sample.difficulty, 'beginner');
|
|
||||||
expect(sample.widgets, hasLength(2));
|
|
||||||
expect(sample.widgets.first, 'AnimatedBuilder');
|
|
||||||
expect(sample.packages, hasLength(2));
|
|
||||||
expect(sample.packages.first, 'json_serializable');
|
|
||||||
expect(sample.tags, hasLength(3));
|
|
||||||
expect(sample.tags[1], 'kittens');
|
|
||||||
expect(sample.platforms, hasLength(3));
|
|
||||||
expect(sample.type, 'sample');
|
|
||||||
expect(sample.date, DateTime.parse('2019-12-15T02:59:43.1Z'));
|
|
||||||
expect(sample.channel, 'stable');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('searching', () {
|
|
||||||
test('search attributes', () async {
|
|
||||||
var file = File('test/yaml/single.yaml');
|
|
||||||
var contents = await file.readAsString();
|
|
||||||
expect(contents, isNotEmpty);
|
|
||||||
|
|
||||||
var index = checkedYamlDecode(
|
|
||||||
contents, (m) => m != null ? Index.fromJson(m) : null,
|
|
||||||
sourceUrl: file.uri);
|
|
||||||
if (index == null) {
|
|
||||||
throw ('unable to load YAML from $file');
|
|
||||||
}
|
|
||||||
var sample = index.samples.first;
|
|
||||||
expect(
|
|
||||||
sample.searchAttributes.split(' '),
|
|
||||||
containsAll(const <String>[
|
|
||||||
'kittens',
|
|
||||||
'tag:beginner',
|
|
||||||
'tag:kittens',
|
|
||||||
'tag:cats',
|
|
||||||
|
|
||||||
// Verify tags are searchable without the prefix
|
|
||||||
'beginner',
|
|
||||||
'kittens',
|
|
||||||
'cats',
|
|
||||||
|
|
||||||
'platform:web',
|
|
||||||
'platform:ios',
|
|
||||||
'platform:android',
|
|
||||||
|
|
||||||
// Verify platforms are searchable without the prefix
|
|
||||||
'web',
|
|
||||||
'ios',
|
|
||||||
'android',
|
|
||||||
|
|
||||||
'widget:AnimatedBuilder',
|
|
||||||
'widget:FutureBuilder',
|
|
||||||
'package:json_serializable',
|
|
||||||
'package:path',
|
|
||||||
|
|
||||||
'type:sample',
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('matchesQuery', () {
|
|
||||||
var attributes = 'kittens '
|
|
||||||
'tag:beginner '
|
|
||||||
'tag:kittens '
|
|
||||||
'tag:cats '
|
|
||||||
'platform:web '
|
|
||||||
'platform:ios '
|
|
||||||
'platform:android '
|
|
||||||
'widget:AnimatedBuilder '
|
|
||||||
'widget:FutureBuilder '
|
|
||||||
'package:json_serializable '
|
|
||||||
'package:path '
|
|
||||||
'type:sample';
|
|
||||||
|
|
||||||
// Test if various queries match these attributes
|
|
||||||
expect(matchesQuery('foo', attributes), false);
|
|
||||||
expect(matchesQuery('Foo', attributes), false);
|
|
||||||
expect(matchesQuery('kittens', attributes), true);
|
|
||||||
expect(matchesQuery('Kittens', attributes), true);
|
|
||||||
expect(matchesQuery('tag:cats', attributes), true);
|
|
||||||
expect(matchesQuery('tag:dogs', attributes), false);
|
|
||||||
expect(matchesQuery('package:path', attributes), true);
|
|
||||||
|
|
||||||
// Test if partial queries match these attributes
|
|
||||||
expect(matchesQuery('kitten', attributes), true);
|
|
||||||
|
|
||||||
// Test if multiple keywords match
|
|
||||||
expect(matchesQuery('kittens tag:cats', attributes), true);
|
|
||||||
expect(matchesQuery('kitten tag:cats', attributes), true);
|
|
||||||
expect(matchesQuery('tag:beginner dogs', attributes), false);
|
|
||||||
expect(matchesQuery('asdf ', attributes), false);
|
|
||||||
|
|
||||||
// Test if queries match by type
|
|
||||||
expect(matchesQuery('type:sample', attributes), true);
|
|
||||||
expect(matchesQuery('type:demo', attributes), false);
|
|
||||||
expect(matchesQuery('kittens type:demo', attributes), false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('Hash parameters', () {
|
|
||||||
test('can be parsed', () {
|
|
||||||
expect(parseHash('#?search=kittens&platform=web'),
|
|
||||||
containsPair('search', 'kittens'));
|
|
||||||
expect(parseHash('#?search=kittens&platform=web'),
|
|
||||||
containsPair('platform', 'web'));
|
|
||||||
expect(parseHash('#?type=sample'), containsPair('type', 'sample'));
|
|
||||||
expect(parseHash('#?type=demo'), containsPair('type', 'demo'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can be set', () {
|
|
||||||
expect(
|
|
||||||
formatHash({
|
|
||||||
'search': 'kittens',
|
|
||||||
'platform': 'web',
|
|
||||||
}),
|
|
||||||
equals('?search=kittens&platform=web'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates search attributes', () {
|
|
||||||
expect(
|
|
||||||
searchQueryFromParams({
|
|
||||||
'search': 'kittens',
|
|
||||||
'platform': 'web',
|
|
||||||
'type': 'sample',
|
|
||||||
}),
|
|
||||||
equals('kittens type:sample platform:web'));
|
|
||||||
expect(
|
|
||||||
searchQueryFromParams({
|
|
||||||
'search': 'kittens',
|
|
||||||
}),
|
|
||||||
equals('kittens'));
|
|
||||||
expect(searchQueryFromParams({}), equals(''));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
samples:
|
|
||||||
# Bad type, should be string, but it's a number.
|
|
||||||
- name: 42
|
|
||||||
screenshots:
|
|
||||||
- url: https://placekitten.com/500/500
|
|
||||||
alt: a kitten
|
|
||||||
- url: https://placekitten.com/400/400
|
|
||||||
alt: another kitten
|
|
||||||
source: http://github.com/johnpryan/kittens
|
|
||||||
description: A sample kitten app
|
|
||||||
difficulty: beginner
|
|
||||||
widgets:
|
|
||||||
- AnimatedBuilder
|
|
||||||
- FutureBuilder
|
|
||||||
packages:
|
|
||||||
- json_serializable
|
|
||||||
- path
|
|
||||||
tags: ['beginner', 'kittens', 'cats']
|
|
||||||
platforms: ['web', 'ios', 'android']
|
|
||||||
links:
|
|
||||||
- text: inspiration
|
|
||||||
href: https://apps.apple.com/us/app/neko-atsume-kitty-collector/id923917775
|
|
||||||
- text: author
|
|
||||||
href: http://jpryan.me
|
|
||||||
type: sample # sample or app
|
|
||||||
date: 2019-12-15T02:59:43.1Z
|
|
||||||
channel: stable
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
samples:
|
|
||||||
- name: Kittens
|
|
||||||
screenshots:
|
|
||||||
- url: https://placekitten.com/500/500
|
|
||||||
alt: a kitten
|
|
||||||
- url: https://placekitten.com/400/400
|
|
||||||
alt: another kitten
|
|
||||||
source: https://github.com/johnpryan/kittens
|
|
||||||
description: A sample kitten app
|
|
||||||
difficulty: beginner
|
|
||||||
widgets:
|
|
||||||
- AnimatedBuilder
|
|
||||||
- FutureBuilder
|
|
||||||
packages:
|
|
||||||
- json_serializable
|
|
||||||
- path
|
|
||||||
tags: ['beginner', 'kittens', 'cats']
|
|
||||||
platforms: ['web', 'ios', 'android']
|
|
||||||
type: sample # sample or app
|
|
||||||
date: 2019-12-15T02:59:43.1Z
|
|
||||||
channel: stable
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
// Copyright 2020 The Flutter team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:grinder/grinder.dart';
|
|
||||||
import 'package:image/image.dart' as image;
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:samples_index/samples_index.dart';
|
|
||||||
import 'package:samples_index/src/templates.dart' as templates;
|
|
||||||
|
|
||||||
Future<void> main(List<String> args) => grind(args);
|
|
||||||
|
|
||||||
@Task('Run tests in the VM')
|
|
||||||
Future<void> testCli() async =>
|
|
||||||
await TestRunner().testAsync(platformSelector: 'vm');
|
|
||||||
|
|
||||||
@Task()
|
|
||||||
void analyze() {
|
|
||||||
run('dart', arguments: const ['analyze', '--fatal-infos', '.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Task('deploy')
|
|
||||||
@Depends(analyze, testCli, generate, buildRelease)
|
|
||||||
void deploy() {
|
|
||||||
log('All tasks completed. ');
|
|
||||||
log('');
|
|
||||||
}
|
|
||||||
|
|
||||||
@Task('Run build_runner to public/ directory')
|
|
||||||
@Depends(createThumbnails)
|
|
||||||
Future<void> buildRelease() async {
|
|
||||||
var app = PubApp.local('build_runner');
|
|
||||||
await app.runAsync(
|
|
||||||
'build --release --output web:public --delete-conflicting-outputs'
|
|
||||||
.split(' ')
|
|
||||||
.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@DefaultTask('Build the project.')
|
|
||||||
@Depends(clean)
|
|
||||||
Future<void> generate() async {
|
|
||||||
var samples = await getSamples();
|
|
||||||
log('Generating index for ${samples.length} samples...');
|
|
||||||
var outputFile = File('web/index.html');
|
|
||||||
await outputFile.create(recursive: true);
|
|
||||||
await outputFile.writeAsString(templates.index(samples));
|
|
||||||
var futures = <Future<void>>[];
|
|
||||||
for (final sample in samples) {
|
|
||||||
var file = File('web/${sample.filename}.html');
|
|
||||||
var future = file.create(recursive: true).then((_) async {
|
|
||||||
await file.writeAsString(templates.description(sample));
|
|
||||||
});
|
|
||||||
futures.add(future);
|
|
||||||
}
|
|
||||||
await Future.wait<void>(futures);
|
|
||||||
log('Generated index for ${samples.length} samples.');
|
|
||||||
}
|
|
||||||
|
|
||||||
@Task('creates thumbnail images in web/images')
|
|
||||||
Future<void> createThumbnails() async {
|
|
||||||
await _createThumbnails(Directory('web/images'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a thumbnail image for each png file
|
|
||||||
Future<void> _createThumbnails(Directory directory) async {
|
|
||||||
var files = await directory.list().toList();
|
|
||||||
var filesToWrite = <Future<void>>{};
|
|
||||||
|
|
||||||
for (final entity in files) {
|
|
||||||
var extension = path.extension(entity.path);
|
|
||||||
var filename = path.basenameWithoutExtension(entity.path);
|
|
||||||
if (extension != '.png' || entity is! File || filename.endsWith('_thumb')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pathPrefix = path.dirname(entity.path);
|
|
||||||
var thumbnailFile = File(path.join(pathPrefix, '${filename}_thumb.png'));
|
|
||||||
|
|
||||||
var img = image.decodeImage(await entity.readAsBytes());
|
|
||||||
var resized = image.copyResize(img!, width: 640);
|
|
||||||
filesToWrite.add(thumbnailFile.writeAsBytes(image.encodePng(resized)));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Future.wait<void>(filesToWrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Task('remove generated HTML files')
|
|
||||||
Future<void> clean() async {
|
|
||||||
var tasks = <Future<void>>[];
|
|
||||||
await for (final file in Directory('web').list(recursive: true)) {
|
|
||||||
if (path.extension(file.path) == '.html') {
|
|
||||||
tasks.add(file.delete());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Future.wait<void>(tasks);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// TODO(kevmoo): https://github.com/flutter/samples/issues/2582
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
import 'dart:html';
|
|
||||||
|
|
||||||
import 'package:mdc_web/mdc_web.dart';
|
|
||||||
import 'package:samples_index/src/carousel.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
querySelectorAll('.mdc-card__primary-action').forEach((el) => MDCRipple(el));
|
|
||||||
|
|
||||||
// Initialize carousel
|
|
||||||
// This carousel will use the div slider-container as the base
|
|
||||||
// withArrowKeyControl is used to listen for arrow key up events
|
|
||||||
Carousel.init(withArrowKeyControl: true);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 574 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 920 KiB |
|
Before Width: | Height: | Size: 846 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 717 KiB |
|
Before Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 99 KiB |