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 `///`).
pull/2617/head
Eric Windmill 4 months ago committed by GitHub
parent 7d211b9371
commit dd21cee9c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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

@ -37,17 +37,6 @@ jobs:
channel: ${{ matrix.flutter_version }}
- 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:
# runs-on: ubuntu-latest
# if: github.repository == 'flutter/samples'

@ -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"
"veggieseasons"
"web_embedding/element_embedding_demo"
"web/_tool"
# TODO(ewindmill): dart:html is deprecated. Delete samples_index
# "web/samples_index"
)
ci_projects "beta" "${PROJECT_NAMES[@]}"

@ -67,9 +67,6 @@ declare -ar PROJECT_NAMES=(
"testing_app"
"veggieseasons"
"web_embedding/element_embedding_demo"
"web/_tool"
# TODO(ewindmill): dart:html is deprecated. Delete samples_index
# "web/samples_index"
)
ci_projects "master" "${PROJECT_NAMES[@]}"

@ -61,9 +61,6 @@ declare -ar PROJECT_NAMES=(
"testing_app"
"veggieseasons"
"web_embedding/element_embedding_demo"
"web/_tool"
# TODO(ewindmill): dart:html is deprecated. Delete samples_index
# "web/samples_index"
)
ci_projects "stable" "${PROJECT_NAMES[@]}"

@ -57,8 +57,6 @@ declare -ar PROJECT_NAMES=(
"testing_app"
"veggieseasons"
"web_embedding/element_embedding_demo"
"web/_tool"
"web/samples_index"
)
echo "--- Running flutter clean and flutter pub get for each sample ---"

1
web/.gitignore vendored

@ -1 +0,0 @@
*/build

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

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

@ -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);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save