Starter
This commit is contained in:
parent
f0c323b3c3
commit
63592a2994
|
|
@ -1,66 +0,0 @@
|
|||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,105 @@
|
|||
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';
|
||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/stub_list_item.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/stub_list_vm.dart';
|
||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||
|
||||
class StubList extends StatelessWidget {
|
||||
final StubListVM viewModel;
|
||||
|
||||
StubList({
|
||||
const StubList({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (! viewModel.isLoaded) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
if (!viewModel.isLoaded) {
|
||||
return LoadingIndicator();
|
||||
} else if (viewModel.stubList.isEmpty) {
|
||||
return Opacity(
|
||||
opacity: 0.5,
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalization.of(context).noRecordsFound,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _buildListView(context);
|
||||
}
|
||||
|
||||
void _showMenu(BuildContext context, StubEntity stub) async {
|
||||
final user = viewModel.user;
|
||||
final message = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
||||
user.canCreate(EntityType.stub)
|
||||
? ListTile(
|
||||
leading: Icon(Icons.control_point_duplicate),
|
||||
title: Text(AppLocalization.of(context).clone),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, stub, EntityAction.clone),
|
||||
)
|
||||
: Container(),
|
||||
Divider(),
|
||||
user.canEditEntity(stub) && !stub.isActive
|
||||
? ListTile(
|
||||
leading: Icon(Icons.restore),
|
||||
title: Text(AppLocalization.of(context).restore),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, stub, EntityAction.restore),
|
||||
)
|
||||
: Container(),
|
||||
user.canEditEntity(stub) && stub.isActive
|
||||
? ListTile(
|
||||
leading: Icon(Icons.archive),
|
||||
title: Text(AppLocalization.of(context).archive),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, stub, EntityAction.archive),
|
||||
)
|
||||
: Container(),
|
||||
user.canEditEntity(stub) && !stub.isDeleted
|
||||
? ListTile(
|
||||
leading: Icon(Icons.delete),
|
||||
title: Text(AppLocalization.of(context).delete),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, stub, EntityAction.delete),
|
||||
)
|
||||
: Container(),
|
||||
]));
|
||||
if (message != null) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
message: message,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
final stubId = viewModel.stubList[index];
|
||||
final stub = viewModel.stubMap[stubId];
|
||||
return Column(children: <Widget>[
|
||||
StubItem(
|
||||
StubListItem(
|
||||
user: viewModel.user,
|
||||
filter: viewModel.filter,
|
||||
stub: stub,
|
||||
onDismissed: (DismissDirection direction) =>
|
||||
viewModel.onDismissed(context, stub, direction),
|
||||
onTap: () => viewModel.onStubTap(context, stub),
|
||||
onLongPress: () => _showMenu(context, stub),
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
import 'package:invoiceninja_flutter/ui/app/entity_state_label.dart';
|
||||
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
|
||||
|
||||
class StubListItem extends StatelessWidget {
|
||||
final UserEntity user;
|
||||
final DismissDirectionCallback onDismissed;
|
||||
final GestureTapCallback onTap;
|
||||
final GestureTapCallback onLongPress;
|
||||
//final ValueChanged<bool> onCheckboxChanged;
|
||||
final StubEntity stub;
|
||||
final String filter;
|
||||
|
||||
static final stubItemKey = (int id) => Key('__stub_item_${id}__');
|
||||
|
||||
const StubListItem({
|
||||
@required this.user,
|
||||
@required this.onDismissed,
|
||||
@required this.onTap,
|
||||
@required this.onLongPress,
|
||||
//@required this.onCheckboxChanged,
|
||||
@required this.stub,
|
||||
@required this.filter,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filterMatch = filter != null && filter.isNotEmpty
|
||||
? stub.matchesFilterValue(filter)
|
||||
: null;
|
||||
final subtitle = filterMatch ?? stub.notes;
|
||||
|
||||
return DismissibleEntity(
|
||||
user: user,
|
||||
entity: stub,
|
||||
onDismissed: onDismissed,
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
/*
|
||||
leading: Checkbox(
|
||||
//key: NinjaKeys.stubItemCheckbox(stub.id),
|
||||
value: true,
|
||||
//onChanged: onCheckboxChanged,
|
||||
onChanged: (value) {
|
||||
return true;
|
||||
},
|
||||
),
|
||||
*/
|
||||
title: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
stub.stubKey,
|
||||
//key: NinjaKeys.clientItemClientKey(client.id),
|
||||
style: Theme.of(context).textTheme.title,
|
||||
),
|
||||
),
|
||||
Text(formatNumber(stub.cost, context),
|
||||
style: Theme.of(context).textTheme.title),
|
||||
],
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
subtitle != null && subtitle.isNotEmpty ?
|
||||
Text(
|
||||
subtitle,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
) : Container(),
|
||||
EntityStateLabel(stub),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
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';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:invoiceninja_flutter/utils/completers.dart';
|
||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||
import 'package:invoiceninja_flutter/redux/stub/stub_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/stub_list.dart';
|
||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||
import 'package:invoiceninja_flutter/redux/stub/stub_actions.dart';
|
||||
|
||||
class StubListBuilder extends StatelessWidget {
|
||||
static final String route = '/stubs/edit';
|
||||
StubListBuilder({Key key}) : super(key: key);
|
||||
const StubListBuilder({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StoreConnector<AppState, StubListVM>(
|
||||
converter: StubListVM.fromStore,
|
||||
builder: (context, vm) {
|
||||
builder: (context, viewModel) {
|
||||
return StubList(
|
||||
viewModel: vm,
|
||||
viewModel: viewModel,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -30,59 +30,106 @@ class StubListBuilder extends StatelessWidget {
|
|||
}
|
||||
|
||||
class StubListVM {
|
||||
final UserEntity user;
|
||||
final List<int> stubList;
|
||||
final BuiltMap<int, StubEntity> stubMap;
|
||||
final String filter;
|
||||
final bool isLoading;
|
||||
final bool isLoaded;
|
||||
final Function(BuildContext, StubEntity) onStubTap;
|
||||
final Function(BuildContext, StubEntity, DismissDirection) onDismissed;
|
||||
final Function(BuildContext) onRefreshed;
|
||||
final Function(BuildContext, StubEntity, EntityAction) onEntityAction;
|
||||
|
||||
StubListVM({
|
||||
@required this.user,
|
||||
@required this.stubList,
|
||||
@required this.stubMap,
|
||||
@required this.filter,
|
||||
@required this.isLoading,
|
||||
@required this.isLoaded,
|
||||
@required this.onStubTap,
|
||||
@required this.onDismissed,
|
||||
@required this.onRefreshed,
|
||||
@required this.onEntityAction,
|
||||
});
|
||||
|
||||
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)));
|
||||
});
|
||||
if (store.state.isLoading) {
|
||||
return Future<Null>(null);
|
||||
}
|
||||
final completer = snackBarCompleter(
|
||||
context, AppLocalization.of(context).refreshComplete);
|
||||
store.dispatch(LoadStubs(completer: completer, force: true));
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
final state = store.state;
|
||||
|
||||
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,
|
||||
user: state.user,
|
||||
stubList: memoizedFilteredStubList(state.stubState.map,
|
||||
state.stubState.list, state.stubListState),
|
||||
stubMap: state.stubState.map,
|
||||
isLoading: state.isLoading,
|
||||
isLoaded: state.stubState.isLoaded,
|
||||
filter: state.stubUIState.listUIState.filter,
|
||||
onStubTap: (context, stub) {
|
||||
store.dispatch(ViewStub(stub: stub, context: context));
|
||||
store.dispatch(EditStub(stub: stub, context: context));
|
||||
},
|
||||
onEntityAction: (context, stub, action) {
|
||||
switch (action) {
|
||||
case EntityAction.clone:
|
||||
Navigator.of(context).pop();
|
||||
store.dispatch(
|
||||
EditStub(context: context, stub: stub.clone));
|
||||
break;
|
||||
case EntityAction.restore:
|
||||
store.dispatch(RestoreStubRequest(
|
||||
popCompleter(
|
||||
context, AppLocalization.of(context).restoredStub),
|
||||
stub.id));
|
||||
break;
|
||||
case EntityAction.archive:
|
||||
store.dispatch(ArchiveStubRequest(
|
||||
popCompleter(
|
||||
context, AppLocalization.of(context).archivedStub),
|
||||
stub.id));
|
||||
break;
|
||||
case EntityAction.delete:
|
||||
store.dispatch(DeleteStubRequest(
|
||||
popCompleter(
|
||||
context, AppLocalization.of(context).deletedStub),
|
||||
stub.id));
|
||||
break;
|
||||
}
|
||||
},
|
||||
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)));
|
||||
});
|
||||
final localization = AppLocalization.of(context);
|
||||
if (direction == DismissDirection.endToStart) {
|
||||
if (stub.isDeleted || stub.isArchived) {
|
||||
store.dispatch(RestoreStubRequest(
|
||||
snackBarCompleter(context, localization.restoredStub),
|
||||
stub.id));
|
||||
} else {
|
||||
store.dispatch(ArchiveStubRequest(
|
||||
snackBarCompleter(context, localization.archivedStub),
|
||||
stub.id));
|
||||
}
|
||||
} else if (direction == DismissDirection.startToEnd) {
|
||||
if (stub.isDeleted) {
|
||||
store.dispatch(RestoreStubRequest(
|
||||
snackBarCompleter(context, localization.restoredStub),
|
||||
stub.id));
|
||||
} else {
|
||||
store.dispatch(DeleteStubRequest(
|
||||
snackBarCompleter(context, localization.deletedStub),
|
||||
stub.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,83 @@
|
|||
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';
|
||||
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/list_filter_button.dart';
|
||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/stub_list_vm.dart';
|
||||
import 'package:invoiceninja_flutter/redux/stub/stub_actions.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/app_drawer_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
|
||||
import 'package:invoiceninja_flutter/utils/keys.dart';
|
||||
|
||||
class StubScreen extends StatelessWidget {
|
||||
static final String route = '/stub';
|
||||
static const String route = '/stub';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var store = StoreProvider.of<AppState>(context);
|
||||
final store = StoreProvider.of<AppState>(context);
|
||||
final company = store.state.selectedCompany;
|
||||
final user = company.user;
|
||||
final localization = AppLocalization.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AppSearch(
|
||||
entityType: EntityType.stub,
|
||||
onSearchChanged: (value) {
|
||||
store.dispatch(SearchStubs(value));
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
AppSearchButton(
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: ListFilter(
|
||||
entityType: EntityType.stub,
|
||||
onSearchPressed: (value) {
|
||||
store.dispatch(SearchStubs(value));
|
||||
onFilterChanged: (value) {
|
||||
store.dispatch(FilterStubs(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',
|
||||
actions: [
|
||||
ListFilterButton(
|
||||
entityType: EntityType.stub,
|
||||
onFilterPressed: (String value) {
|
||||
store.dispatch(FilterStubs(value));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: AppDrawerBuilder(),
|
||||
body: StubListBuilder(),
|
||||
bottomNavigationBar: AppBottomBar(
|
||||
entityType: EntityType.stub,
|
||||
onSelectedSortField: (value) => store.dispatch(SortStubs(value)),
|
||||
customValues1: company.getCustomFieldValues(CustomFieldType.stub1,
|
||||
excludeBlank: true),
|
||||
customValues2: company.getCustomFieldValues(CustomFieldType.stub2,
|
||||
excludeBlank: true),
|
||||
onSelectedCustom1: (value) =>
|
||||
store.dispatch(FilterStubsByCustom1(value)),
|
||||
onSelectedCustom2: (value) =>
|
||||
store.dispatch(FilterStubsByCustom2(value)),
|
||||
sortFields: [
|
||||
StubFields.stubKey,
|
||||
StubFields.cost,
|
||||
StubFields.updatedAt,
|
||||
],
|
||||
onSelectedState: (EntityState state, value) {
|
||||
store.dispatch(FilterStubsByState(state));
|
||||
},
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: user.canCreate(EntityType.stub)
|
||||
? FloatingActionButton(
|
||||
key: Key(StubKeys.stubScreenFABKeyString),
|
||||
backgroundColor: Theme.of(context).primaryColorDark,
|
||||
onPressed: () {
|
||||
store.dispatch(
|
||||
EditStub(stub: StubEntity(), context: context));
|
||||
},
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: localization.newStub,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue