Starter
This commit is contained in:
parent
d44662baff
commit
86a3cea1b7
57
make.sh
57
make.sh
|
|
@ -1,57 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
module="$1"
|
||||
Module="$(tr '[:lower:]' '[:upper:]' <<< ${module:0:1})${module:1}"
|
||||
|
||||
[ $# -eq 0 ] && { echo "Usage: $0 module-name"; exit 1; }
|
||||
|
||||
## Create new directories
|
||||
|
||||
if [ ! -d "lib/redux/$module" ]
|
||||
then
|
||||
echo "Creating directory: lib/redux/$module"
|
||||
mkdir "lib/redux/$module"
|
||||
fi
|
||||
|
||||
if [ ! -d "lib/ui/$module" ]
|
||||
then
|
||||
echo "Creating directory: lib/ui/$module"
|
||||
mkdir "lib/ui/$module"
|
||||
fi
|
||||
|
||||
if [ ! -d "lib/ui/$module/edit" ]
|
||||
then
|
||||
echo "Creating directory: lib/ui/$module/edit"
|
||||
mkdir "lib/ui/$module/edit"
|
||||
fi
|
||||
|
||||
## Create new files
|
||||
|
||||
declare -a files=(
|
||||
#'lib/data/models/product_model.dart'
|
||||
#'lib/data/repositories/product_repository.dart'
|
||||
'lib/redux/product/product_actions.dart'
|
||||
'lib/redux/product/product_reducer.dart'
|
||||
'lib/redux/product/product_state.dart'
|
||||
'lib/redux/product/product_middleware.dart'
|
||||
'lib/redux/product/product_selectors.dart'
|
||||
'lib/ui/product/edit/product_edit.dart'
|
||||
'lib/ui/product/edit/product_edit_vm.dart'
|
||||
'lib/ui/product/product_item.dart'
|
||||
'lib/ui/product/product_list_vm.dart'
|
||||
'lib/ui/product/product_list.dart'
|
||||
'lib/ui/product/product_screen.dart')
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
filename=$(echo $i | sed "s/product/$module/g")
|
||||
echo "Creating file: $filename"
|
||||
cp $i $filename
|
||||
sed -i "s/product/$module/g" $filename
|
||||
sed -i "s/Product/$Module/g" $filename
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,379 @@
|
|||
#!/bin/bash
|
||||
|
||||
# https://github.com/hillelcoren/flutter-redux-starter
|
||||
echo "Flutter/Redux Starter by @hillelcoren"
|
||||
|
||||
[ $# -eq 0 ] && { echo "Usage: $0 init or $0 make <module-name>"; exit 1; }
|
||||
|
||||
action="$1"
|
||||
lineBreak='\'$'\n'
|
||||
|
||||
if [ ${action} = "init" ]; then
|
||||
|
||||
company="$2"
|
||||
package="$3"
|
||||
url="$4"
|
||||
|
||||
echo "Company: $company"
|
||||
echo "Package: $package"
|
||||
echo "URL: $url"
|
||||
|
||||
flutter pub get
|
||||
|
||||
echo "Creating files..."
|
||||
|
||||
sed -i -e "s/__API_URL__/$url/g" ./lib/constants.dart
|
||||
|
||||
mv "./android/app/src/main/java/com/hillelcoren" "./android/app/src/main/java/com/$company"
|
||||
mv "./android/app/src/main/java/com/$company/flutterreduxstarter" "./android/app/src/main/java/com/$company/$package"
|
||||
|
||||
# Replace 'hillelcoren'
|
||||
declare -a files=(
|
||||
'./ios/Runner.xcodeproj/project.pbxproj'
|
||||
'./android/app/build.gradle'
|
||||
'./android/app/src/main/AndroidManifest.xml'
|
||||
"./android/app/src/main/java/com/$company/$package/MainActivity.java")
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
sed -i -e "s/hillelcoren/$company/g" $i
|
||||
done
|
||||
|
||||
# Replace 'flutterReduxStarter'
|
||||
declare -a files=(
|
||||
"./android/app/src/main/java/com/$company/$package/MainActivity.java")
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
sed -i -e "s/flutterReduxStarter/$package/g" $i
|
||||
done
|
||||
|
||||
# Replace 'flutterreduxstarter'
|
||||
declare -a files=(
|
||||
"./ios/Runner.xcodeproj/project.pbxproj")
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
sed -i -e "s/flutterreduxstarter/$package/g" $i
|
||||
done
|
||||
|
||||
declare -a files=(
|
||||
'./.packages'
|
||||
'./pubspec.yaml'
|
||||
'./ios/Runner/Info.plist'
|
||||
'./ios/Flutter/Generated.xcconfig'
|
||||
'./android/app/build.gradle'
|
||||
'./android/app/src/main/AndroidManifest.xml'
|
||||
'./lib/main.dart'
|
||||
'./lib/redux/app/app_state.dart'
|
||||
'./lib/redux/app/app_reducer.dart'
|
||||
'./lib/redux/app/app_actions.dart'
|
||||
'./lib/redux/app/app_middleware.dart'
|
||||
'./lib/redux/app/data_reducer.dart'
|
||||
'./lib/redux/auth/auth_state.dart'
|
||||
'./lib/redux/auth/auth_actions.dart'
|
||||
'./lib/redux/auth/auth_middleware.dart'
|
||||
'./lib/redux/auth/auth_reducer.dart'
|
||||
'./lib/redux/ui/ui_actions.dart'
|
||||
'./lib/redux/ui/ui_reducer.dart'
|
||||
'./lib/redux/ui/entity_ui_state.dart'
|
||||
'./lib/redux/ui/list_ui_state.dart'
|
||||
'./lib/data/repositories/auth_repository.dart'
|
||||
'./lib/data/repositories/persistence_repository.dart'
|
||||
'./lib/data/models/serializers.dart'
|
||||
'./test/login_test.dart'
|
||||
'./lib/redux/ui/ui_state.dart'
|
||||
'./lib/ui/auth/login.dart'
|
||||
'./lib/ui/auth/login_vm.dart'
|
||||
'./lib/ui/app/app_drawer.dart'
|
||||
'./lib/ui/app/init.dart'
|
||||
'./lib/ui/app/app_drawer_vm.dart'
|
||||
'./lib/ui/app/actions_menu_button.dart'
|
||||
'./lib/ui/app/app_bottom_bar.dart'
|
||||
'./lib/ui/app/app_search.dart'
|
||||
'./lib/ui/app/app_search_button.dart'
|
||||
'./lib/ui/app/dismissible_entity.dart'
|
||||
'./lib/ui/home/home_screen.dart'
|
||||
'./stubs/data/models/stub_model'
|
||||
'./stubs/data/repositories/stub_repository'
|
||||
'./stubs/redux/stub/stub_actions'
|
||||
'./stubs/redux/stub/stub_reducer'
|
||||
'./stubs/redux/stub/stub_state'
|
||||
'./stubs/redux/stub/stub_middleware'
|
||||
'./stubs/redux/stub/stub_selectors'
|
||||
'./stubs/ui/stub/edit/stub_edit'
|
||||
'./stubs/ui/stub/edit/stub_edit_vm'
|
||||
'./stubs/ui/stub/view/stub_view'
|
||||
'./stubs/ui/stub/view/stub_view_vm'
|
||||
'./stubs/ui/stub/stub_item'
|
||||
'./stubs/ui/stub/stub_list_vm'
|
||||
'./stubs/ui/stub/stub_list'
|
||||
'./stubs/ui/stub/stub_screen')
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
sed -i -e "s/flutter_redux_starter/$package/g" $i
|
||||
done
|
||||
|
||||
else
|
||||
|
||||
package="$2"
|
||||
module="$3"
|
||||
Module="$(tr '[:lower:]' '[:upper:]' <<< ${module:0:1})${module:1}"
|
||||
fields="$4"
|
||||
IFS=', ' read -r -a fieldsArray <<< "$fields"
|
||||
|
||||
echo "Make..."
|
||||
echo "Creating $module module"
|
||||
|
||||
# Create new directories
|
||||
if [ ! -d "lib/redux/$module" ]
|
||||
then
|
||||
echo "Creating directory: lib/redux/$module"
|
||||
mkdir "lib/redux/$module"
|
||||
fi
|
||||
|
||||
if [ ! -d "lib/ui/$module" ]
|
||||
then
|
||||
echo "Creating directory: lib/ui/$module"
|
||||
mkdir "lib/ui/$module"
|
||||
fi
|
||||
|
||||
if [ ! -d "lib/ui/$module/view" ]
|
||||
then
|
||||
echo "Creating directory: lib/ui/$module/view"
|
||||
mkdir "lib/ui/$module/view"
|
||||
fi
|
||||
|
||||
if [ ! -d "lib/ui/$module/edit" ]
|
||||
then
|
||||
echo "Creating directory: lib/ui/$module/edit"
|
||||
mkdir "lib/ui/$module/edit"
|
||||
fi
|
||||
|
||||
# Create new module files
|
||||
declare -a files=(
|
||||
'./stubs/data/models/stub_model'
|
||||
'./stubs/data/repositories/stub_repository'
|
||||
'./stubs/redux/stub/stub_actions'
|
||||
'./stubs/redux/stub/stub_reducer'
|
||||
'./stubs/redux/stub/stub_state'
|
||||
'./stubs/redux/stub/stub_middleware'
|
||||
'./stubs/redux/stub/stub_selectors'
|
||||
'./stubs/ui/stub/edit/stub_edit'
|
||||
'./stubs/ui/stub/edit/stub_edit_vm'
|
||||
'./stubs/ui/stub/view/stub_view'
|
||||
'./stubs/ui/stub/view/stub_view_vm'
|
||||
'./stubs/ui/stub/stub_item'
|
||||
'./stubs/ui/stub/stub_list_vm'
|
||||
'./stubs/ui/stub/stub_list'
|
||||
'./stubs/ui/stub/stub_screen')
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
filename=$(echo $i | sed "s/stubs/lib/g" | sed "s/stub/$module/g")
|
||||
echo "Creating file: $filename.dart"
|
||||
cp $i "$filename.dart"
|
||||
sed -i -e "s/stub/$module/g" "$filename.dart"
|
||||
sed -i -e "s/Stub/$Module/g" "$filename.dart"
|
||||
done
|
||||
|
||||
# Link in new module
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/redux\/${module}\/${module}_state.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/app_state.dart
|
||||
|
||||
comment="STARTER: states switch - do not remove comment"
|
||||
code="case EntityType.${module}:${lineBreak}return ${module}UIState;${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/app_state.dart
|
||||
|
||||
comment="STARTER: state getters - do not remove comment"
|
||||
code="${Module}State get ${module}State => this.dataState.${module}State;${lineBreak}"
|
||||
code="${code}ListUIState get ${module}ListState => this.uiState.${module}UIState.listUIState;${lineBreak}"
|
||||
code="${code}${Module}UIState get ${module}UIState => this.uiState.${module}UIState;${lineBreak}${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/app_state.dart
|
||||
|
||||
for (( idx=${#fieldsArray[@]}-1 ; idx>=0 ; idx-- )) ; do
|
||||
elements="${fieldsArray[idx]}"
|
||||
IFS=':' read -r -a elementArray <<< "$elements"
|
||||
|
||||
element="${elementArray[0]}"
|
||||
type="${elementArray[1]}"
|
||||
Element="$(tr '[:lower:]' '[:upper:]' <<< ${element:0:1})${element:1}"
|
||||
|
||||
comment="STARTER: fields - do not remove comment"
|
||||
code="static const String ${element} = '${element}';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
|
||||
comment="STARTER: properties - do not remove comment"
|
||||
code="String get ${element};${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
|
||||
comment="STARTER: sort switch - do not remove comment"
|
||||
code="case ${Module}Fields.${element}:${lineBreak}"
|
||||
code="${code}response = ${module}A.${element}.compareTo(${module}B.${element});${lineBreak}break;${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
|
||||
comment="STARTER: search - do not remove comment"
|
||||
code="if (${element}.toLowerCase().contains(search)){${lineBreak}"
|
||||
code="${code}return true;${lineBreak}"
|
||||
code="${code}}${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
|
||||
comment="STARTER: constructor - do not remove comment"
|
||||
code="${element}: '',${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
|
||||
comment="STARTER: controllers - do not remove comment"
|
||||
code="final _${element}Controller = TextEditingController();${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/edit/${module}_edit.dart"
|
||||
|
||||
comment="STARTER: array - do not remove comment"
|
||||
code="_${element}Controller,${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/edit/${module}_edit.dart"
|
||||
|
||||
comment="STARTER: read value - do not remove comment"
|
||||
code="_${element}Controller.text = ${module}.${element};${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/edit/${module}_edit.dart"
|
||||
|
||||
comment="STARTER: set value - do not remove comment"
|
||||
code="..${element} = _${element}Controller.text.trim()${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/edit/${module}_edit.dart"
|
||||
|
||||
comment="STARTER: widgets - do not remove comment"
|
||||
code="TextFormField(${lineBreak}"
|
||||
code="${code}controller: _${element}Controller,${lineBreak}"
|
||||
code="${code}autocorrect: false,${lineBreak}"
|
||||
if [ "$type" = "textarea" ]; then
|
||||
code="${code}maxLines: 4,${lineBreak}"
|
||||
fi
|
||||
code="${code}decoration: InputDecoration(${lineBreak}"
|
||||
code="${code}labelText: '${Element}',${lineBreak}"
|
||||
code="${code}),${lineBreak}"
|
||||
code="${code}),${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/edit/${module}_edit.dart"
|
||||
|
||||
comment="STARTER: widgets - do not remove comment"
|
||||
if [ ${element} = ${fieldsArray[0]} ]; then
|
||||
code="Text(${module}.${element}, style: Theme.of(context).textTheme.title),${lineBreak}"
|
||||
code="${code}SizedBox(height: 12.0),${lineBreak}"
|
||||
else
|
||||
code="Text(${module}.${element}),"
|
||||
fi
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/view/${module}_view.dart"
|
||||
|
||||
comment="STARTER: sort - do not remove comment"
|
||||
code="${Module}Fields.${element},${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/${module}_screen.dart"
|
||||
|
||||
if [ "$idx" -eq 0 ]; then
|
||||
comment="STARTER: sort default - do not remove comment"
|
||||
code="return ${module}A.${element}.compareTo(${module}B.${element});${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
|
||||
comment="STARTER: display name - do not remove comment"
|
||||
code="return ${element};${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/data/models/${module}_model.dart"
|
||||
fi
|
||||
|
||||
if [ "$idx" -eq 1 ]; then
|
||||
comment="STARTER: subtitle - do not remove comment"
|
||||
code="subtitle: Text(${module}.${element}, maxLines: 4),${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" "./lib/ui/${module}/${module}_item.dart"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/ui\/${module}\/${module}_screen.dart';${lineBreak}"
|
||||
code="${code}import 'package:${package}\/ui\/${module}\/edit\/${module}_edit_vm.dart';${lineBreak}"
|
||||
code="${code}import 'package:${package}\/ui\/${module}\/view\/${module}_view_vm.dart';${lineBreak}"
|
||||
code="${code}import 'package:${package}\/redux\/${module}\/${module}_actions.dart';${lineBreak}"
|
||||
code="${code}import 'package:${package}\/redux\/${module}\/${module}_middleware.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/main.dart
|
||||
|
||||
comment="STARTER: middleware - do not remove comment"
|
||||
code="..addAll(createStore${Module}sMiddleware())${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/main.dart
|
||||
|
||||
comment="STARTER: routes - do not remove comment"
|
||||
code="${Module}Screen.route: (context) {${lineBreak}"
|
||||
code="${code}widget.store.dispatch(Load${Module}s());${lineBreak}"
|
||||
code="${code}return ${Module}Screen();${lineBreak}"
|
||||
code="${code}},${lineBreak}"
|
||||
code="${code}${Module}ViewScreen.route: (context) => ${Module}ViewScreen(),${lineBreak}"
|
||||
code="${code}${Module}EditScreen.route: (context) => ${Module}EditScreen(),${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/main.dart
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/data\/models\/${module}_model.dart';${lineBreak}"
|
||||
code="${code}import 'package:${package}\/redux\/${module}\/${module}_state.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/data/models/serializers.dart
|
||||
|
||||
comment="STARTER: serializers - do not remove comment"
|
||||
code="${Module}Entity,${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/data/models/serializers.dart
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/redux\/${module}\/${module}_state.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/data_state.dart
|
||||
|
||||
comment="STARTER: fields - do not remove comment"
|
||||
code="${Module}State get ${module}State;${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/data_state.dart
|
||||
|
||||
comment="STARTER: constructor - do not remove comment"
|
||||
code="${module}State: ${Module}State(),${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/data_state.dart
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/redux\/${module}\/${module}_reducer.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/data_reducer.dart
|
||||
|
||||
comment="STARTER: reducer - do not remove comment"
|
||||
code="..${module}State.replace(${module}sReducer(state.${module}State, action))${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/data_reducer.dart
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/redux\/${module}\/${module}_actions.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/ui/app/app_drawer.dart
|
||||
|
||||
comment="STARTER: menu - do not remove comment"
|
||||
code="ListTile(${lineBreak}"
|
||||
code="${code}leading: Icon(Icons.widgets),${lineBreak}"
|
||||
code="${code}title: Text('${Module}s'),${lineBreak}"
|
||||
code="${code}onTap: () => store.dispatch(View${Module}List(context)),${lineBreak}"
|
||||
code="${code}),${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/ui/app/app_drawer.dart
|
||||
|
||||
comment="STARTER: types - do not remove comment"
|
||||
code="static const EntityType ${module} = _$"
|
||||
code="${code}${module};${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/data/models/models.dart
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/redux\/${module}\/${module}_state.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/ui/ui_state.dart
|
||||
|
||||
comment="STARTER: properties - do not remove comment"
|
||||
code="${Module}UIState get ${module}UIState;${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/ui/ui_state.dart
|
||||
|
||||
comment="STARTER: constructor - do not remove comment"
|
||||
code="${module}UIState: ${Module}UIState(),${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/ui/ui_state.dart
|
||||
|
||||
comment="STARTER: import - do not remove comment"
|
||||
code="import 'package:${package}\/redux\/${module}\/${module}_reducer.dart';${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/ui/ui_reducer.dart
|
||||
|
||||
comment="STARTER: reducer - do not remove comment"
|
||||
code="..${module}UIState.replace(${module}UIReducer(state.${module}UIState, action))${lineBreak}"
|
||||
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/ui/ui_reducer.dart
|
||||
|
||||
echo "Generating built files.."
|
||||
flutter packages pub run build_runner clean
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
fi
|
||||
|
||||
echo "Successfully completed"
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import 'package:built_value/built_value.dart';
|
||||
import 'package:built_value/serializer.dart';
|
||||
import 'package:flutter_redux_starter/data/models/models.dart';
|
||||
|
||||
part 'stub_model.g.dart';
|
||||
|
||||
class StubFields {
|
||||
// STARTER: fields - do not remove comment
|
||||
}
|
||||
|
||||
abstract class StubEntity extends Object with BaseEntity implements Built<StubEntity, StubEntityBuilder> {
|
||||
|
||||
// STARTER: properties - do not remove comment
|
||||
|
||||
static int counter = 0;
|
||||
factory StubEntity() {
|
||||
return _$StubEntity._(
|
||||
id: 0,
|
||||
// STARTER: constructor - do not remove comment
|
||||
);
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
// STARTER: display name - do not remove comment
|
||||
}
|
||||
|
||||
int compareTo(StubEntity stub, String sortField, bool sortAscending) {
|
||||
int response = 0;
|
||||
StubEntity stubA = sortAscending ? this : stub;
|
||||
StubEntity stubB = sortAscending ? stub: this;
|
||||
|
||||
switch (sortField) {
|
||||
// STARTER: sort switch - do not remove comment
|
||||
}
|
||||
|
||||
if (response == 0) {
|
||||
// STARTER: sort default - do not remove comment
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
bool matchesSearch(String search) {
|
||||
if (search == null || search.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
search = search.toLowerCase();
|
||||
|
||||
// STARTER: search - do not remove comment
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
StubEntity._();
|
||||
static Serializer<StubEntity> get serializer => _$stubEntitySerializer;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import 'dart:async';
|
||||
import 'dart:core';
|
||||
import 'dart:convert';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:flutter_redux_starter/data/models/models.dart';
|
||||
import 'package:flutter_redux_starter/data/models/serializers.dart';
|
||||
import 'package:flutter_redux_starter/redux/auth/auth_state.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/data/web_client.dart';
|
||||
import 'package:flutter_redux_starter/constants.dart';
|
||||
|
||||
class StubRepository {
|
||||
final WebClient webClient;
|
||||
|
||||
const StubRepository({
|
||||
this.webClient = const WebClient(),
|
||||
});
|
||||
|
||||
Future<BuiltList<StubEntity>> loadList(AuthState auth) async {
|
||||
final response = await webClient.get(kApiUrl + '/stubs');
|
||||
|
||||
var list = new BuiltList<StubEntity>(response.map((stub) {
|
||||
return serializers.deserializeWith(StubEntity.serializer, stub);
|
||||
}));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
Future saveData(AuthState auth, StubEntity stub, [EntityAction action]) async {
|
||||
|
||||
var data = serializers.serializeWith(StubEntity.serializer, stub);
|
||||
var response;
|
||||
|
||||
if (stub.isNew) {
|
||||
response = await webClient.post(
|
||||
kApiUrl + '/stubs', json.encode(data));
|
||||
} else {
|
||||
var url = kApiUrl + '/stubs/' + stub.id.toString();
|
||||
response = await webClient.put(url, json.encode(data));
|
||||
}
|
||||
|
||||
return serializers.deserializeWith(StubEntity.serializer, response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_actions.dart';
|
||||
|
||||
|
||||
class ViewStubList implements PersistUI {
|
||||
final BuildContext context;
|
||||
ViewStubList(this.context);
|
||||
}
|
||||
|
||||
class ViewStub implements PersistUI {
|
||||
final StubEntity stub;
|
||||
final BuildContext context;
|
||||
ViewStub({this.stub, this.context});
|
||||
}
|
||||
|
||||
class EditStub implements PersistUI {
|
||||
final StubEntity stub;
|
||||
final BuildContext context;
|
||||
EditStub({this.stub, this.context});
|
||||
}
|
||||
|
||||
|
||||
class LoadStubs {
|
||||
final Completer completer;
|
||||
final bool force;
|
||||
|
||||
LoadStubs([this.completer, this.force = false]);
|
||||
}
|
||||
|
||||
class LoadStubsRequest implements StartLoading {}
|
||||
|
||||
class LoadStubsFailure implements StopLoading {
|
||||
final dynamic error;
|
||||
LoadStubsFailure(this.error);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadStubsFailure{error: $error}';
|
||||
}
|
||||
}
|
||||
|
||||
class LoadStubsSuccess implements StopLoading, PersistData {
|
||||
final BuiltList<StubEntity> stubs;
|
||||
LoadStubsSuccess(this.stubs);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadStubsSuccess{stubs: $stubs}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UpdateStub implements PersistUI {
|
||||
final StubEntity stub;
|
||||
UpdateStub(this.stub);
|
||||
}
|
||||
|
||||
class SaveStubRequest implements StartLoading {
|
||||
final Completer completer;
|
||||
final StubEntity stub;
|
||||
SaveStubRequest({this.completer, this.stub});
|
||||
}
|
||||
|
||||
class AddStubSuccess implements StopLoading, PersistData {
|
||||
final StubEntity stub;
|
||||
AddStubSuccess(this.stub);
|
||||
}
|
||||
|
||||
|
||||
class SaveStubSuccess implements StopLoading, PersistData {
|
||||
final StubEntity stub;
|
||||
|
||||
SaveStubSuccess(this.stub);
|
||||
}
|
||||
|
||||
class SaveStubFailure implements StopLoading {
|
||||
final String error;
|
||||
SaveStubFailure (this.error);
|
||||
}
|
||||
|
||||
class DeleteStubRequest implements StartLoading {
|
||||
final Completer completer;
|
||||
final int stubId;
|
||||
|
||||
DeleteStubRequest(this.completer, this.stubId);
|
||||
}
|
||||
|
||||
class DeleteStubSuccess implements StopLoading, PersistData {
|
||||
final StubEntity stub;
|
||||
DeleteStubSuccess(this.stub);
|
||||
}
|
||||
|
||||
class DeleteStubFailure implements StopLoading {
|
||||
final StubEntity stub;
|
||||
DeleteStubFailure(this.stub);
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SearchStubs {
|
||||
final String search;
|
||||
SearchStubs(this.search);
|
||||
}
|
||||
|
||||
class SortStubs implements PersistUI {
|
||||
final String field;
|
||||
SortStubs(this.field);
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_screen.dart';
|
||||
import 'package:flutter_redux_starter/data/models/models.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/ui_actions.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit_vm.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/view/stub_view_vm.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/data/repositories/stub_repository.dart';
|
||||
|
||||
List<Middleware<AppState>> createStoreStubsMiddleware([
|
||||
StubRepository repository = const StubRepository(),
|
||||
]) {
|
||||
final viewStubList = _viewStubList();
|
||||
final viewStub = _viewStub();
|
||||
final editStub = _editStub();
|
||||
final loadStubs = _loadStubs(repository);
|
||||
final saveStub = _saveStub(repository);
|
||||
final deleteStub = _deleteStub(repository);
|
||||
|
||||
return [
|
||||
TypedMiddleware<AppState, ViewStubList>(viewStubList),
|
||||
TypedMiddleware<AppState, ViewStub>(viewStub),
|
||||
TypedMiddleware<AppState, EditStub>(editStub),
|
||||
TypedMiddleware<AppState, LoadStubs>(loadStubs),
|
||||
TypedMiddleware<AppState, SaveStubRequest>(saveStub),
|
||||
TypedMiddleware<AppState, DeleteStubRequest>(deleteStub),
|
||||
];
|
||||
}
|
||||
|
||||
Middleware<AppState> _viewStubList() {
|
||||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
next(action);
|
||||
|
||||
store.dispatch(UpdateCurrentRoute(StubScreen.route));
|
||||
Navigator.of(action.context).pushReplacementNamed(StubScreen.route);
|
||||
};
|
||||
}
|
||||
|
||||
Middleware<AppState> _viewStub() {
|
||||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
next(action);
|
||||
|
||||
store.dispatch(UpdateCurrentRoute(StubViewScreen.route));
|
||||
Navigator.of(action.context).pushNamed(StubViewScreen.route);
|
||||
};
|
||||
}
|
||||
|
||||
Middleware<AppState> _editStub() {
|
||||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
next(action);
|
||||
|
||||
store.dispatch(UpdateCurrentRoute(StubEditScreen.route));
|
||||
Navigator.of(action.context).pushNamed(StubEditScreen.route);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Middleware<AppState> _deleteStub(StubRepository repository) {
|
||||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
var origStub = store.state.stubState.map[action.stubId];
|
||||
repository
|
||||
.saveData(store.state.authState,
|
||||
origStub, EntityAction.delete)
|
||||
.then((stub) {
|
||||
store.dispatch(DeleteStubSuccess(stub));
|
||||
if (action.completer != null) {
|
||||
action.completer.complete(null);
|
||||
}
|
||||
}).catchError((error) {
|
||||
print(error);
|
||||
store.dispatch(DeleteStubFailure(origStub));
|
||||
});
|
||||
|
||||
next(action);
|
||||
};
|
||||
}
|
||||
|
||||
Middleware<AppState> _saveStub(StubRepository repository) {
|
||||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
repository
|
||||
.saveData(
|
||||
store.state.authState, action.stub)
|
||||
.then((stub) {
|
||||
if (action.stub.isNew) {
|
||||
store.dispatch(AddStubSuccess(stub));
|
||||
} else {
|
||||
store.dispatch(SaveStubSuccess(stub));
|
||||
}
|
||||
action.completer.complete(null);
|
||||
}).catchError((error) {
|
||||
print(error);
|
||||
store.dispatch(SaveStubFailure(error));
|
||||
});
|
||||
|
||||
next(action);
|
||||
};
|
||||
}
|
||||
|
||||
Middleware<AppState> _loadStubs(StubRepository repository) {
|
||||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
|
||||
AppState state = store.state;
|
||||
|
||||
if (!state.stubState.isStale && !action.force) {
|
||||
next(action);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.isLoading) {
|
||||
next(action);
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch(LoadStubsRequest());
|
||||
repository
|
||||
.loadList(state.authState)
|
||||
.then((data) {
|
||||
store.dispatch(LoadStubsSuccess(data));
|
||||
|
||||
if (action.completer != null) {
|
||||
action.completer.complete(null);
|
||||
}
|
||||
}).catchError((error) {
|
||||
print(error);
|
||||
store.dispatch(LoadStubsFailure(error));
|
||||
});
|
||||
|
||||
next(action);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import 'package:redux/redux.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/entity_ui_state.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/list_ui_state.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_state.dart';
|
||||
|
||||
EntityUIState stubUIReducer(StubUIState state, action) {
|
||||
return state.rebuild((b) => b
|
||||
..listUIState.replace(stubListReducer(state.listUIState, action))
|
||||
..selected.replace(editingReducer(state.selected, action))
|
||||
);
|
||||
}
|
||||
|
||||
final editingReducer = combineReducers<StubEntity>([
|
||||
TypedReducer<StubEntity, SaveStubSuccess>(_updateEditing),
|
||||
TypedReducer<StubEntity, AddStubSuccess>(_updateEditing),
|
||||
TypedReducer<StubEntity, ViewStub>(_updateEditing),
|
||||
TypedReducer<StubEntity, EditStub>(_updateEditing),
|
||||
TypedReducer<StubEntity, UpdateStub>(_updateEditing),
|
||||
]);
|
||||
|
||||
/*
|
||||
StubEntity _clearEditing(StubEntity stub, action) {
|
||||
return StubEntity();
|
||||
}
|
||||
*/
|
||||
|
||||
StubEntity _updateEditing(StubEntity stub, action) {
|
||||
return action.stub;
|
||||
}
|
||||
|
||||
final stubListReducer = combineReducers<ListUIState>([
|
||||
TypedReducer<ListUIState, SortStubs>(_sortStubs),
|
||||
TypedReducer<ListUIState, SearchStubs>(_searchStubs),
|
||||
]);
|
||||
|
||||
ListUIState _searchStubs(ListUIState stubListState, SearchStubs action) {
|
||||
return stubListState.rebuild((b) => b
|
||||
..search = action.search
|
||||
);
|
||||
}
|
||||
|
||||
ListUIState _sortStubs(ListUIState stubListState, SortStubs action) {
|
||||
return stubListState.rebuild((b) => b
|
||||
..sortAscending = b.sortField != action.field || ! b.sortAscending
|
||||
..sortField = action.field
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
final stubsReducer = combineReducers<StubState>([
|
||||
TypedReducer<StubState, SaveStubSuccess>(_updateStub),
|
||||
TypedReducer<StubState, AddStubSuccess>(_addStub),
|
||||
TypedReducer<StubState, LoadStubsSuccess>(_setLoadedStubs),
|
||||
TypedReducer<StubState, LoadStubsFailure>(_setNoStubs),
|
||||
|
||||
TypedReducer<StubState, DeleteStubRequest>(_deleteStubRequest),
|
||||
TypedReducer<StubState, DeleteStubSuccess>(_deleteStubSuccess),
|
||||
TypedReducer<StubState, DeleteStubFailure>(_deleteStubFailure),
|
||||
]);
|
||||
|
||||
StubState _deleteStubRequest(StubState stubState, DeleteStubRequest action) {
|
||||
var stub = stubState.map[action.stubId].rebuild((b) => b
|
||||
);
|
||||
|
||||
return stubState.rebuild((b) => b
|
||||
..map[action.stubId] = stub
|
||||
);
|
||||
}
|
||||
|
||||
StubState _deleteStubSuccess(StubState stubState, DeleteStubSuccess action) {
|
||||
return stubState.rebuild((b) => b
|
||||
..map[action.stub.id] = action.stub
|
||||
);
|
||||
}
|
||||
|
||||
StubState _deleteStubFailure(StubState stubState, DeleteStubFailure action) {
|
||||
return stubState.rebuild((b) => b
|
||||
..map[action.stub.id] = action.stub
|
||||
);
|
||||
}
|
||||
|
||||
StubState _addStub(
|
||||
StubState stubState, AddStubSuccess action) {
|
||||
return stubState.rebuild((b) => b
|
||||
..map[action.stub.id] = action.stub
|
||||
..list.add(action.stub.id)
|
||||
);
|
||||
}
|
||||
|
||||
StubState _updateStub(
|
||||
StubState stubState, SaveStubSuccess action) {
|
||||
return stubState.rebuild((b) => b
|
||||
..map[action.stub.id] = action.stub
|
||||
);
|
||||
}
|
||||
|
||||
StubState _setNoStubs(
|
||||
StubState stubState, LoadStubsFailure action) {
|
||||
return stubState;
|
||||
}
|
||||
|
||||
StubState _setLoadedStubs(
|
||||
StubState stubState, LoadStubsSuccess action) {
|
||||
return stubState.rebuild(
|
||||
(b) => b
|
||||
..lastUpdated = DateTime.now().millisecondsSinceEpoch
|
||||
..map.addAll(Map.fromIterable(
|
||||
action.stubs,
|
||||
key: (item) => item.id,
|
||||
value: (item) => item,
|
||||
))
|
||||
..list.replace(action.stubs.map(
|
||||
(stub) => stub.id).toList())
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:memoize/memoize.dart';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/list_ui_state.dart';
|
||||
|
||||
var memoizedStubList = memo3((
|
||||
BuiltMap<int, StubEntity> stubMap,
|
||||
BuiltList<int> stubList,
|
||||
ListUIState stubListState) => visibleStubsSelector(stubMap, stubList, stubListState)
|
||||
);
|
||||
|
||||
List<int> visibleStubsSelector(
|
||||
BuiltMap<int, StubEntity> stubMap,
|
||||
BuiltList<int> stubList,
|
||||
ListUIState stubListState) {
|
||||
|
||||
var list = stubList.where((stubId) {
|
||||
var stub = stubMap[stubId];
|
||||
return stub.matchesSearch(stubListState.search);
|
||||
}).toList();
|
||||
|
||||
list.sort((stubAId, stubBId) {
|
||||
var stubA = stubMap[stubAId];
|
||||
var stubB = stubMap[stubBId];
|
||||
return stubA.compareTo(stubB, stubListState.sortField, stubListState.sortAscending);
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import 'package:built_value/built_value.dart';
|
||||
import 'package:built_value/serializer.dart';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:flutter_redux_starter/constants.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/entity_ui_state.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/list_ui_state.dart';
|
||||
|
||||
part 'stub_state.g.dart';
|
||||
|
||||
abstract class StubState implements Built<StubState, StubStateBuilder> {
|
||||
|
||||
@nullable
|
||||
int get lastUpdated;
|
||||
|
||||
BuiltMap<int, StubEntity> get map;
|
||||
BuiltList<int> get list;
|
||||
|
||||
factory StubState() {
|
||||
return _$StubState._(
|
||||
map: BuiltMap<int, StubEntity>(),
|
||||
list: BuiltList<int>(),
|
||||
);
|
||||
}
|
||||
|
||||
bool get isStale {
|
||||
if (! isLoaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return DateTime.now().millisecondsSinceEpoch - lastUpdated > kMillisecondsToRefreshData;
|
||||
}
|
||||
|
||||
bool get isLoaded {
|
||||
return lastUpdated != null;
|
||||
}
|
||||
|
||||
StubState._();
|
||||
static Serializer<StubState> get serializer => _$stubStateSerializer;
|
||||
}
|
||||
|
||||
abstract class StubUIState extends Object with EntityUIState implements Built<StubUIState, StubUIStateBuilder> {
|
||||
|
||||
@nullable
|
||||
StubEntity get selected;
|
||||
|
||||
bool get isSelectedNew => selected.isNew;
|
||||
|
||||
factory StubUIState() {
|
||||
return _$StubUIState._(
|
||||
listUIState: ListUIState(''),
|
||||
selected: StubEntity(),
|
||||
);
|
||||
}
|
||||
|
||||
StubUIState._();
|
||||
static Serializer<StubUIState> get serializer => _$stubUIStateSerializer;
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/form_card.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit_vm.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/save_icon_button.dart';
|
||||
|
||||
class StubEdit extends StatefulWidget {
|
||||
final StubEditVM viewModel;
|
||||
|
||||
StubEdit({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StubEditState createState() => _StubEditState();
|
||||
}
|
||||
|
||||
class _StubEditState extends State<StubEdit> {
|
||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
// STARTER: controllers - do not remove comment
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
|
||||
_controllers = [
|
||||
// STARTER: array - do not remove comment
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var stub = widget.viewModel.stub;
|
||||
// STARTER: read value - do not remove comment
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
var stub = widget.viewModel.stub.rebuild((b) => b
|
||||
// STARTER: set value - do not remove comment
|
||||
);
|
||||
if (stub != widget.viewModel.stub) {
|
||||
widget.viewModel.onChanged(stub);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var viewModel = widget.viewModel;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
viewModel.onBackPressed();
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(viewModel.stub.isNew
|
||||
? 'New Stub'
|
||||
: viewModel.stub.displayName),
|
||||
actions: <Widget>[
|
||||
Builder(builder: (BuildContext context) {
|
||||
return SaveIconButton(
|
||||
isLoading: viewModel.isLoading,
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewModel.onSavePressed(context);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
FormCard(
|
||||
children: <Widget>[
|
||||
// STARTER: widgets - do not remove comment
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/ui_actions.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_screen.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
||||
|
||||
class StubEditScreen extends StatelessWidget {
|
||||
static final String route = '/stub/edit';
|
||||
StubEditScreen({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StoreConnector<AppState, StubEditVM>(
|
||||
converter: (Store<AppState> store) {
|
||||
return StubEditVM.fromStore(store);
|
||||
},
|
||||
builder: (context, vm) {
|
||||
return StubEdit(
|
||||
viewModel: vm,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StubEditVM {
|
||||
final StubEntity stub;
|
||||
final Function(StubEntity) onChanged;
|
||||
final Function(BuildContext) onSavePressed;
|
||||
final Function onBackPressed;
|
||||
final bool isLoading;
|
||||
|
||||
StubEditVM({
|
||||
@required this.stub,
|
||||
@required this.onChanged,
|
||||
@required this.onSavePressed,
|
||||
@required this.onBackPressed,
|
||||
@required this.isLoading,
|
||||
});
|
||||
|
||||
factory StubEditVM.fromStore(Store<AppState> store) {
|
||||
final stub = store.state.stubUIState.selected;
|
||||
|
||||
return StubEditVM(
|
||||
isLoading: store.state.isLoading,
|
||||
stub: stub,
|
||||
onChanged: (StubEntity stub) {
|
||||
store.dispatch(UpdateStub(stub));
|
||||
},
|
||||
onBackPressed: () {
|
||||
store.dispatch(UpdateCurrentRoute(StubScreen.route));
|
||||
},
|
||||
onSavePressed: (BuildContext context) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(SaveStubRequest(completer: completer, stub: stub));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: IconMessage(
|
||||
message: stub.isNew
|
||||
? 'Successfully Created Stub'
|
||||
: 'Successfully Updated Stub',
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
//import 'package:flutter_redux_starter/ui/app/dismissible_entity.dart';
|
||||
|
||||
|
||||
class StubItem extends StatelessWidget {
|
||||
final DismissDirectionCallback onDismissed;
|
||||
final GestureTapCallback onTap;
|
||||
final StubEntity stub;
|
||||
|
||||
static final stubItemKey = (int id) => Key('__stub_item_${id}__');
|
||||
|
||||
StubItem({
|
||||
@required this.onDismissed,
|
||||
@required this.onTap,
|
||||
@required this.stub,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
/*
|
||||
return DismissibleEntity(
|
||||
entity: stub,
|
||||
onDismissed: onDismissed,
|
||||
onTap: onTap,
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
title: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
stub.displayName,
|
||||
style: Theme.of(context).textTheme.title,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// STARTER: subtitle - do not remove comment
|
||||
),
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
title: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
stub.displayName,
|
||||
style: Theme.of(context).textTheme.title,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// STARTER: subtitle - do not remove comment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_item.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_list_vm.dart';
|
||||
|
||||
class StubList extends StatelessWidget {
|
||||
final StubListVM viewModel;
|
||||
|
||||
StubList({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (! viewModel.isLoaded) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return _buildListView(context);
|
||||
}
|
||||
|
||||
Widget _buildListView(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => viewModel.onRefreshed(context),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.stubList.length,
|
||||
itemBuilder: (BuildContext context, index) {
|
||||
var stubId = viewModel.stubList[index];
|
||||
var stub = viewModel.stubMap[stubId];
|
||||
return Column(children: <Widget>[
|
||||
StubItem(
|
||||
stub: stub,
|
||||
onDismissed: (DismissDirection direction) =>
|
||||
viewModel.onDismissed(context, stub, direction),
|
||||
onTap: () => viewModel.onStubTap(context, stub),
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import 'dart:async';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_selectors.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_list.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
|
||||
class StubListBuilder extends StatelessWidget {
|
||||
static final String route = '/stubs/edit';
|
||||
StubListBuilder({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StoreConnector<AppState, StubListVM>(
|
||||
converter: StubListVM.fromStore,
|
||||
builder: (context, vm) {
|
||||
return StubList(
|
||||
viewModel: vm,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StubListVM {
|
||||
final List<int> stubList;
|
||||
final BuiltMap<int, StubEntity> stubMap;
|
||||
final bool isLoading;
|
||||
final bool isLoaded;
|
||||
final Function(BuildContext, StubEntity) onStubTap;
|
||||
final Function(BuildContext, StubEntity, DismissDirection) onDismissed;
|
||||
final Function(BuildContext) onRefreshed;
|
||||
|
||||
StubListVM({
|
||||
@required this.stubList,
|
||||
@required this.stubMap,
|
||||
@required this.isLoading,
|
||||
@required this.isLoaded,
|
||||
@required this.onStubTap,
|
||||
@required this.onDismissed,
|
||||
@required this.onRefreshed,
|
||||
});
|
||||
|
||||
static StubListVM fromStore(Store<AppState> store) {
|
||||
Future<Null> _handleRefresh(BuildContext context) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(LoadStubs(completer, true));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: IconMessage(
|
||||
message: 'Refresh complete',
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
}
|
||||
|
||||
return StubListVM(
|
||||
stubList: memoizedStubList(store.state.stubState.map,
|
||||
store.state.stubState.list, store.state.stubListState),
|
||||
stubMap: store.state.stubState.map,
|
||||
isLoading: store.state.isLoading,
|
||||
isLoaded: store.state.stubState.isLoaded,
|
||||
onStubTap: (context, stub) {
|
||||
store.dispatch(ViewStub(stub: stub, context: context));
|
||||
},
|
||||
onRefreshed: (context) => _handleRefresh(context),
|
||||
onDismissed: (BuildContext context, StubEntity stub,
|
||||
DismissDirection direction) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(DeleteStubRequest(completer, stub.id));
|
||||
var message = 'Successfully Deleted Stub';
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: IconMessage(
|
||||
message: message,
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/app_search.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/app_search_button.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/data/models/models.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_list_vm.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/app_drawer_vm.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/app_bottom_bar.dart';
|
||||
|
||||
class StubScreen extends StatelessWidget {
|
||||
static final String route = '/stub';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var store = StoreProvider.of<AppState>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AppSearch(
|
||||
entityType: EntityType.stub,
|
||||
onSearchChanged: (value) {
|
||||
store.dispatch(SearchStubs(value));
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
AppSearchButton(
|
||||
entityType: EntityType.stub,
|
||||
onSearchPressed: (value) {
|
||||
store.dispatch(SearchStubs(value));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: AppDrawerBuilder(),
|
||||
body: StubListBuilder(),
|
||||
bottomNavigationBar: AppBottomBar(
|
||||
entityType: EntityType.stub,
|
||||
onSelectedSortField: (value) {
|
||||
store.dispatch(SortStubs(value));
|
||||
},
|
||||
sortFields: [
|
||||
// STARTER: sort - do not remove comment
|
||||
],
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: Theme.of(context).primaryColorDark,
|
||||
onPressed: () {
|
||||
store.dispatch(EditStub(stub: StubEntity(), context: context));
|
||||
},
|
||||
child: Icon(Icons.add,color: Colors.white,),
|
||||
tooltip: 'New Stub',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/actions_menu_button.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/view/stub_view_vm.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/form_card.dart';
|
||||
|
||||
class StubView extends StatefulWidget {
|
||||
final StubViewVM viewModel;
|
||||
|
||||
StubView({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StubViewState createState() => new _StubViewState();
|
||||
}
|
||||
|
||||
class _StubViewState extends State<StubView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var viewModel = widget.viewModel;
|
||||
var stub = viewModel.stub;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(stub.displayName),
|
||||
actions: stub.isNew
|
||||
? []
|
||||
: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
viewModel.onEditPressed(context);
|
||||
},
|
||||
),
|
||||
ActionMenuButton(
|
||||
isLoading: viewModel.isLoading,
|
||||
entity: stub,
|
||||
onSelected: viewModel.onActionSelected,
|
||||
)
|
||||
],
|
||||
),
|
||||
body: FormCard(
|
||||
children: [
|
||||
// STARTER: widgets - do not remove comment
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/data/models/models.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/view/stub_view.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
||||
|
||||
class StubViewScreen extends StatelessWidget {
|
||||
static final String route = '/stub/view';
|
||||
StubViewScreen({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StoreConnector<AppState, StubViewVM>(
|
||||
converter: (Store<AppState> store) {
|
||||
return StubViewVM.fromStore(store);
|
||||
},
|
||||
builder: (context, vm) {
|
||||
return StubView(
|
||||
viewModel: vm,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StubViewVM {
|
||||
final StubEntity stub;
|
||||
final Function(BuildContext, EntityAction) onActionSelected;
|
||||
final Function(BuildContext) onEditPressed;
|
||||
final bool isLoading;
|
||||
|
||||
StubViewVM({
|
||||
@required this.stub,
|
||||
@required this.onActionSelected,
|
||||
@required this.onEditPressed,
|
||||
@required this.isLoading,
|
||||
});
|
||||
|
||||
factory StubViewVM.fromStore(Store<AppState> store) {
|
||||
final stub = store.state.stubUIState.selected;
|
||||
|
||||
return StubViewVM(
|
||||
isLoading: store.state.isLoading,
|
||||
stub: stub,
|
||||
onEditPressed: (BuildContext context) {
|
||||
store.dispatch(EditStub(stub: stub, context: context));
|
||||
},
|
||||
onActionSelected: (BuildContext context, EntityAction action) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
var message;
|
||||
switch (action) {
|
||||
case EntityAction.delete:
|
||||
store.dispatch(DeleteStubRequest(completer, stub.id));
|
||||
message = 'Successfully Deleted Stub';
|
||||
break;
|
||||
}
|
||||
if (message != null) {
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: IconMessage(
|
||||
message: message,
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue