Commit f5d2fee9 authored by Brian Egan's avatar Brian Egan

Good progress on vanilla example. Going to move away from immutable state now

parent 058323a9
......@@ -21,18 +21,35 @@ All examples must include a README describing the framework, the general impleme
## Functionality
### Home Screen
The home screen of the app is the "Show all todos".
### No todos
When there are no todos, the `MarkAll`, `TodoList` and `Footer` should be hidden.
When there are no todos
* The Context Menu showing "Mark All Complete"
### New todo
New todos are entered in the `TextField` at the top of the app. The `TextField` should be focused when the app starts. Pressing Enter creates the todo, appends it to the todo list, and clears the TextField. Make sure to `.trim()` the input and then check that it's not empty before creating a new todo.
To add a new Todo, tap the Floating Action Button shown on List view. This button takes the user to the "Add New Todo" screen.
The title of the Todo is entered into the title `TextField` at the top of the screen. The title `TextField` should be focused when the screen opens. In order to add a new todo, the title field must not be empty. Make sure to `.trim()` the input and then check that it's not empty before creating a new todo. If the title contains no text, show an error.
In addition to the title `TextField`, another `TextField` must exist to store Notes related to the todo.
Pressing the "Add Todo" button closes the "Add New Todo" screen and appends the new todo to the List of Todos.
### Mark all as complete
This checkbox toggles all the todos to the same state as itself. Make sure to clear the checked state after the "Clear completed" button is clicked. The "Mark all as complete" checkbox should also be updated when single todo items are checked/unchecked. Eg. When all the todos are checked it should also get checked.
### Clear completed button
Removes completed todos when clicked. Should be hidden when there are no completed todos.
### Item
A todo item has three possible interactions:
......@@ -51,10 +68,6 @@ When editing mode is activated it will hide the checkbox and bring forward an in
Displays the number of active todos in a pluralized form. Make sure to pluralize the `item` word correctly: `0 items`, `1 item`, `2 items`. Example: **2** items left.
### Clear completed button
Removes completed todos when clicked. Should be hidden when there are no completed todos.
### Persistence
Your app should persist the todos and current route. If the framework has capabilities for persisting data, use that. Otherwise, use the provided `TodoStorage` class.
......
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
if ENV['FLUTTER_FRAMEWORK_DIR'] == nil
abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework')
end
target 'Runner' do
# Pods for Runner
# Flutter Pods
pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR']
if File.exists? '../.flutter-plugins'
flutter_root = File.expand_path('..')
File.foreach('../.flutter-plugins') { |line|
plugin = line.split(pattern='=')
if plugin.length == 2
name = plugin[0].strip()
path = plugin[1].strip()
resolved_path = File.expand_path("#{path}/ios", flutter_root)
pod name, :path => resolved_path
else
puts "Invalid plugin specification: #{line}"
end
}
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
PODS:
- Flutter (1.0.0)
- path_provider (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `/Users/phillywiggins/lab/flutter/bin/cache/artifacts/engine/ios`)
- path_provider (from `/Users/phillywiggins/.pub-cache/hosted/pub.dartlang.org/path_provider-0.2.1+1/ios`)
EXTERNAL SOURCES:
Flutter:
:path: /Users/phillywiggins/lab/flutter/bin/cache/artifacts/engine/ios
path_provider:
:path: /Users/phillywiggins/.pub-cache/hosted/pub.dartlang.org/path_provider-0.2.1+1/ios
SPEC CHECKSUMS:
Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf
path_provider: f96fff6166a8867510d2c25fdcc346327cc4b259
PODFILE CHECKSUM: 351e02e34b831289961ec3558a535cbd2c4965d2
COCOAPODS: 1.3.1
......@@ -11,6 +11,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4E0BF3F795C0D5BC036FD0EF /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29EFC0A2F8A8A613327EDD90 /* libPods-Runner.a */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
......@@ -41,6 +42,7 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
29EFC0A2F8A8A613327EDD90 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
......@@ -65,12 +67,28 @@
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
4E0BF3F795C0D5BC036FD0EF /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
3E54B6683A4EA3005FD5F51F /* Pods */ = {
isa = PBXGroup;
children = (
);
name = Pods;
sourceTree = "<group>";
};
6159DDF3EAA7370C039C6230 /* Frameworks */ = {
isa = PBXGroup;
children = (
29EFC0A2F8A8A613327EDD90 /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
......@@ -91,7 +109,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
3E54B6683A4EA3005FD5F51F /* Pods */,
6159DDF3EAA7370C039C6230 /* Frameworks */,
);
sourceTree = "<group>";
};
......@@ -134,12 +153,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
965CF0FDC1DF98EA84C181F2 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
5DF487E2FDC43C0FD214FB44 /* [CP] Embed Pods Frameworks */,
580245905BADB38B4D1E854B /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -214,6 +236,57 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
580245905BADB38B4D1E854B /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
5DF487E2FDC43C0FD214FB44 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
965CF0FDC1DF98EA84C181F2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
......
......@@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/dribbble_auth/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/dribbble_auth/build" />
<excludeFolder url="file://$MODULE_DIR$/example/vanilla/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/vanilla/build" />
</content>
......
// Copyright (c) 2017, phillywiggins. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
/// Support for doing something awesome.
///
/// More dartdocs go here.
/// The core logic and presentation code that each app should feel free to use
/// as a base for .
library flutter_mvc;
export 'src/keys.dart';
......@@ -12,3 +8,4 @@ export 'src/persistence.dart';
export 'src/routes.dart';
export 'src/strings.dart';
export 'src/theme.dart';
export 'src/widgets.dart';
import 'dart:math';
class AppState {
List<Todo> todos;
VisibilityFilter filter;
bool isLoading;
final List<Todo> todos;
final VisibilityFilter activeFilter;
final bool isLoading;
final AppTab activeTab;
AppState([
AppState({
this.todos = const [],
this.filter = VisibilityFilter.all,
this.activeFilter = VisibilityFilter.all,
this.isLoading = false,
]);
this.activeTab = AppTab.todos,
});
factory AppState.loading(List<Todo> todos, VisibilityFilter activeFilter) =>
new AppState(
todos: todos,
activeFilter: activeFilter,
isLoading: true,
);
AppState copyWith({
List<Todo> todos,
VisibilityFilter activeFilter,
bool isLoading,
AppTab activeTab,
}) =>
new AppState(
todos: todos ?? this.todos,
activeFilter: activeFilter ?? this.activeFilter,
isLoading: isLoading ?? this.isLoading,
activeTab: activeTab ?? this.activeTab,
);
AppState toggleAll() {
final allCompleted = this.allComplete;
return copyWith(
todos:
todos.map((todo) => todo.copyWith(complete: !allCompleted)).toList(),
);
}
AppState toggleOne(Todo todo, bool isComplete) {
return copyWith(
todos: todos
.map((t) => t == todo ? t.copyWith(complete: isComplete) : t)
.toList(),
);
}
factory AppState.loading(List<Todo> todos, VisibilityFilter filter) =>
new AppState(todos, filter, true);
AppState clearCompleted() =>
copyWith(todos: todos.where((todo) => !todo.complete).toList());
bool get hasTodos => todos.isNotEmpty;
bool get allCompleted => todos.every((todo) => todo.complete);
bool get allComplete => todos.every((todo) => todo.complete);
bool get allActive => todos.every((todo) => !todo.complete);
bool get hasCompletedTodos => !allActive;
int get numCompleted =>
todos.fold(0, (sum, todo) => todo.complete ? ++sum : sum);
int get numActive =>
todos.fold(0, (sum, todo) => !todo.complete ? ++sum : sum);
List<Todo> get filteredTodos => todos.where((todo) {
if (filter == VisibilityFilter.all) {
if (activeFilter == VisibilityFilter.all) {
return true;
} else if (filter == VisibilityFilter.active) {
} else if (activeFilter == VisibilityFilter.active) {
return !todo.complete;
} else if (filter == VisibilityFilter.completed) {
} else if (activeFilter == VisibilityFilter.completed) {
return todo.complete;
}
}).toList();
@override
String toString() {
return 'AppState{\n todos: $todos,\n filter: $filter\n}';
}
Map<String, Object> toJson() {
return {
"todos": todos.map((todo) => todo.toJson()).toList(),
"filter": filter.toString(),
"activeFilter": activeFilter.toString(),
"activeTab": activeTab.toString(),
};
}
......@@ -42,18 +89,31 @@ class AppState {
final todos = (json["todos"] as List<Map<String, Object>>)
.map((todoJson) => Todo.fromJson(todoJson))
.toList();
final filter = getFilterFromString(json["filter"] as String);
final activeFilter = getFilterFromString(json["activeFilter"] as String);
final activeTab = getTabFromString(json["activeTab"] as String);
return new AppState(
todos: todos, activeFilter: activeFilter, activeTab: activeTab);
}
return new AppState(todos, filter);
static VisibilityFilter getFilterFromString(String activeFilterAsString) {
for (VisibilityFilter activeFilter in VisibilityFilter.values) {
if (activeFilter.toString() == activeFilterAsString) {
return activeFilter;
}
}
return VisibilityFilter.all;
}
static VisibilityFilter getFilterFromString(String filterAsString) {
for (VisibilityFilter filter in VisibilityFilter.values) {
if (filter.toString() == filterAsString) {
return filter;
static AppTab getTabFromString(String tabAsString) {
for (AppTab tab in AppTab.values) {
if (tab.toString() == tabAsString) {
return tab;
}
}
return null;
return AppTab.todos;
}
@override
......@@ -62,55 +122,133 @@ class AppState {
other is AppState &&
runtimeType == other.runtimeType &&
todos == other.todos &&
filter == other.filter;
activeFilter == other.activeFilter &&
isLoading == other.isLoading &&
activeTab == other.activeTab;
@override
int get hashCode => todos.hashCode ^ filter.hashCode;
}
class Todo {
bool complete;
bool editing;
String text;
Todo(
this.text, {
this.complete = false,
this.editing = false,
});
int get hashCode =>
todos.hashCode ^
activeFilter.hashCode ^
isLoading.hashCode ^
activeTab.hashCode;
@override
String toString() {
return 'Todo{\n complete: $complete,\n editing: $editing,\n text: $text\n}';
return 'AppState{todos: $todos, activeFilter: $activeFilter, isLoading: $isLoading, activeTab: $activeTab}';
}
AppState addTodo(Todo todo) {
return copyWith(
todos: ([]
..addAll(todos)
..add(todo)));
}
AppState updateTodo(Todo old, Todo replacement) {
return copyWith(
todos: todos.map((todo) => todo == old ? replacement : todo).toList());
}
AppState removeTodo(Todo todoToRemove) {
return copyWith(
todos: todos.where((todo) => todo != todoToRemove).toList());
}
}
class Todo {
final bool complete;
final String task;
final String note;
final String id;
Todo(this.task, {this.complete = false, this.note = '', String id})
: this.id = id ?? (new Uuid().generateV4());
Todo copyWith({
String task,
bool complete,
String note,
String id,
}) =>
new Todo(task ?? this.task,
complete: complete ?? this.complete,
note: note ?? this.note,
id: id ?? this.id);
Map<String, Object> toJson() {
return {
"complete": complete,
"editing": editing,
"text": text,
"task": task,
"note": note,
"id": id,
};
}
static Todo fromJson(Map<String, Object> json) {
return new Todo(
json["text"] as String,
json["task"] as String,
complete: json["complete"] as bool,
editing: json["editing"] as bool,
note: json["note"] as String,
id: json["id"] as String,
);
}
@override
String toString() {
return 'Todo{complete: $complete, task: $task, note: $note, id: $id}';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Todo &&
runtimeType == other.runtimeType &&
complete == other.complete &&
editing == other.editing &&
text == other.text;
task == other.task &&
note == other.note &&
id == other.id;
@override
int get hashCode => complete.hashCode ^ editing.hashCode ^ text.hashCode;
int get hashCode =>
complete.hashCode ^ task.hashCode ^ note.hashCode ^ id.hashCode;
}
/// A UUID generator. This will generate unique IDs in the format:
///
/// f47ac10b-58cc-4372-a567-0e02b2c3d479
///
/// The generated uuids are 128 bit numbers encoded in a specific string format.
///
/// For more information, see
/// http://en.wikipedia.org/wiki/Universally_unique_identifier.
class Uuid {
final Random _random = new Random();
/// Generate a version 4 (random) uuid. This is a uuid scheme that only uses
/// random numbers as the source of the generated uuid.
String generateV4() {
// Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
final int special = 8 + _random.nextInt(4);
return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
'${_bitsDigits(16, 4)}-'
'4${_bitsDigits(12, 3)}-'
'${_printDigits(special, 1)}${_bitsDigits(12, 3)}-'
'${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
}
String _bitsDigits(int bitCount, int digitCount) =>
_printDigits(_generateBits(bitCount), digitCount);
int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
String _printDigits(int value, int count) =>
value.toRadixString(16).padLeft(count, '0');
}
enum AppTab { todos, stats }
enum VisibilityFilter { all, active, completed }
enum ExtraAction { toggleAllComplete, clearCompleted }
import 'package:flutter_mvc/src/models.dart';
class FlutterMvcStrings {
static final String all = "All";
static final String active = "Active";
static final String completed = "Completed";
static final String todos = "Todos";
static final String stats = "Stats";
static final String all = "Show All";
static final String active = "Show Active";
static final String completed = "Show Completed";
static final String hintText = "What needs to be done?";
static final String markAllComplete = "Mark all complete";
static final String markAllIncomplete = "Mark all incomplete";
static final String clearCompleted = "Clear completed";
static final String addTodo = "Add Todo";
static final String editTodo = "Edit Todo";
static final String saveChanges = "Save changes";
static final String filterTodos = "Filter todos";
String itemsLeft(int numTodos) {
static String itemsLeft(int numTodos) {
return '$numTodos item${numTodos != 1 ? 's' : ''} left';
}
static String tabTitle(AppTab tab) => tab == AppTab.todos ? todos : stats;
}
import 'package:flutter/material.dart';
class FlutterMvcTheme {
static final theme = new ThemeData(
primarySwatch: Colors.blueGrey,
static final theme = new ThemeData.dark().copyWith(
primaryColor: Colors.grey[800],
accentColor: Colors.cyan[300],
buttonColor: Colors.grey[800],
textSelectionColor: Colors.cyan[100],
);
}
export 'widgets/filter_button.dart';
export 'widgets/extra_actions_button.dart';
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_mvc/flutter_mvc.dart';
class ExtraActionsButton extends StatelessWidget {
final PopupMenuItemSelected<ExtraAction> onSelected;
final bool allComplete;
final bool hasCompletedTodos;
ExtraActionsButton({
this.onSelected,
this.allComplete = false,
this.hasCompletedTodos = true,
Key key,
})
: super(key: key);
@override
Widget build(BuildContext context) {
return new PopupMenuButton<ExtraAction>(
onSelected: onSelected,
itemBuilder: (BuildContext context) => <PopupMenuItem<ExtraAction>>[
new PopupMenuItem<ExtraAction>(
value: ExtraAction.toggleAllComplete,
child: new Text(allComplete ? FlutterMvcStrings.markAllIncomplete : FlutterMvcStrings.markAllComplete),
),
new PopupMenuItem<ExtraAction>(
value: ExtraAction.clearCompleted,
child: new Text(FlutterMvcStrings.clearCompleted),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_mvc/flutter_mvc.dart';
class FilterButton extends StatelessWidget {
final PopupMenuItemSelected<VisibilityFilter> onSelected;
final VisibilityFilter activeFilter;
FilterButton({this.onSelected, this.activeFilter, Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final defaultStyle = Theme.of(context).textTheme.body1;
final activeStyle = Theme
.of(context)
.textTheme
.body1
.copyWith(color: Theme.of(context).accentColor);
return new PopupMenuButton<VisibilityFilter>(
tooltip: FlutterMvcStrings.filterTodos,
onSelected: onSelected,
itemBuilder: (BuildContext context) => <PopupMenuItem<VisibilityFilter>>[
new PopupMenuItem<VisibilityFilter>(
value: VisibilityFilter.all,
child: new Text(
FlutterMvcStrings.all,
style: activeFilter == VisibilityFilter.all
? activeStyle
: defaultStyle,
),
),
new PopupMenuItem<VisibilityFilter>(
value: VisibilityFilter.active,
child: new Text(
FlutterMvcStrings.active,
style: activeFilter == VisibilityFilter.active
? activeStyle
: defaultStyle,
),
),
new PopupMenuItem<VisibilityFilter>(
value: VisibilityFilter.completed,
child: new Text(
FlutterMvcStrings.completed,
style: activeFilter == VisibilityFilter.completed
? activeStyle
: defaultStyle,
),
),
],
icon: new Icon(Icons.filter_list),
);
}
}
......@@ -8,6 +8,7 @@ environment:
sdk: '>=1.20.1 <2.0.0'