-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
1,021 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
## 0.0.1 | ||
## 0.0.3 | ||
* Better demos | ||
|
||
## 0.0.2+3 | ||
* First release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:nav_stack/nav_stack.dart'; | ||
|
||
import 'advanced_tabs_demo_pages.dart'; | ||
|
||
/// This btn reads the global path to figure out if it should be "selected", | ||
/// On pressed, it updates the global path with it's .target value. | ||
/// It wraps Expanded() and is mean to be placed in a Row() widget. | ||
/// All Scaffolds use this button in their tab menus. | ||
class NavBtn extends StatelessWidget { | ||
final String target; | ||
final String? selectionAlias; | ||
final String label; | ||
|
||
const NavBtn(this.label, {Key? key, required this.target, this.selectionAlias}) : super(key: key); | ||
@override | ||
Widget build(BuildContext context) { | ||
// We're selected if the current path matches our target, or our alias | ||
bool isSelected = NavStack.of(context).path.contains(selectionAlias ?? target); | ||
TextStyle textStyle = TextStyle(fontSize: 22, color: isSelected ? Colors.black : Colors.blue); | ||
return Expanded( | ||
child: OutlinedButton( | ||
onPressed: () => NavStack.of(context).path = target, | ||
child: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Text(label, style: textStyle))), | ||
); | ||
} | ||
} | ||
|
||
/// This buttons goes back one level in the nav-stack, and auto-hides itself on Web | ||
class BackBtn extends StatelessWidget { | ||
Widget build(BuildContext context) => | ||
kIsWeb ? SizedBox() : OutlinedButton(onPressed: NavStack.of(context).goBack, child: Text("<< Back")); | ||
} | ||
|
||
/// This buttons opens the details view and passes it as a parameter like `/details/92` | ||
class OpenDetailsBtn extends StatelessWidget { | ||
const OpenDetailsBtn({Key? key, required this.child, required this.itemId}) : super(key: key); | ||
final Widget child; | ||
final String itemId; | ||
|
||
Widget build(BuildContext context) => | ||
TextButton(onPressed: () => NavStack.of(context).path = "${DetailsPage.path}$itemId", child: child); | ||
} | ||
|
||
/// This buttons pops all details views, allowing us to jump back to whichever root opened them in the first place | ||
class CloseDetailsBtn extends StatelessWidget { | ||
Widget build(BuildContext context) => | ||
CloseButton(onPressed: () => NavStack.of(context).popMatching(DetailsPage.path)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:nav_stack/nav_stack.dart'; | ||
|
||
import 'advanced_tabs_demo_pages.dart'; | ||
import 'advanced_tabs_demo_scaffold.dart'; | ||
|
||
class AdvancedTabsDemo extends StatelessWidget { | ||
@override | ||
Widget build(BuildContext context) { | ||
return NavStack( | ||
initialPath: AppPaths.tabsCategory + HomePage.path, | ||
stackBuilder: (_, controller) { | ||
void _handleComposePressed() => controller.path = ComposePage.path; | ||
return PathStack( | ||
routes: { | ||
[AppPaths.tabsCategory]: PathStack( | ||
// Main scaffold is wrapped here | ||
scaffoldBuilder: (_, stack) => MainScaffold( | ||
child: stack, | ||
currentPath: NavStack.of(context).path, | ||
// Goto /compose when this is pressed | ||
onComposePressed: _handleComposePressed, | ||
), | ||
transitionBuilder: (_, stack, animation) => FadeTransition(opacity: animation, child: stack), | ||
routes: { | ||
// Home | ||
[HomePage.path]: HomePage("").buildStackRoute(), | ||
// Settings | ||
[AppPaths.settings]: PathStack( | ||
// Settings scaffold is wrapped here | ||
scaffoldBuilder: (_, child) => SettingsScaffold(child: child), | ||
routes: { | ||
[ProfileSettings.path]: ProfileSettings("").buildStackRoute(), | ||
[AlertSettings.path]: AlertSettings("").buildStackRoute(), | ||
[BillingSettings.path]: BillingSettings("").buildStackRoute(maintainState: false), | ||
}, | ||
).buildStackRoute(), | ||
// Inbox | ||
[AppPaths.inbox]: PathStack( | ||
scaffoldBuilder: (_, child) => InboxScaffold(child: child), | ||
routes: { | ||
[InboxPage.friendsPath]: InboxPage(InboxType.friends).buildStackRoute(), | ||
[InboxPage.unreadPath]: InboxPage(InboxType.unread).buildStackRoute(), | ||
[InboxPage.archivedPath]: InboxPage(InboxType.archived).buildStackRoute(), | ||
}, | ||
).buildStackRoute(), | ||
}, | ||
).buildStackRoute(), | ||
|
||
/// Non stateful full-screen route | ||
[ComposePage.path]: ComposePage().buildStackRoute(maintainState: false), | ||
|
||
/// Inject itemId param into detailsView | ||
[DetailsPage.path + ":id"]: StackRouteBuilder( | ||
maintainState: false, | ||
builder: (_, args) => DetailsPage(itemId: args["id"]), | ||
) | ||
}, | ||
); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:cached_network_image/cached_network_image.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:nav_stack/nav_stack.dart'; | ||
|
||
import 'advanced_tabs_buttons.dart'; | ||
|
||
/// Sample Content Pages | ||
class AppPaths { | ||
static const String tabsCategory = "tabs/"; | ||
static const String settings = "settings/"; | ||
static const String inbox = "inbox/"; | ||
} | ||
|
||
class SomeStatefulPage extends StatefulWidget { | ||
SomeStatefulPage(String title, {Key? key}) : super(key: key) { | ||
this.title = title; | ||
print("${this} TITLE = $title"); | ||
} | ||
late final String title; | ||
|
||
@override | ||
_SomeStatefulPageState createState() => _SomeStatefulPageState(); | ||
} | ||
|
||
class _SomeStatefulPageState extends State<SomeStatefulPage> { | ||
String _filter = ""; | ||
List<String>? items; | ||
TextEditingController txtController = TextEditingController(); | ||
@override | ||
void initState() { | ||
super.initState(); | ||
Future.delayed(Duration(seconds: 1), () { | ||
if (mounted == false) return; | ||
setState(() => items = List.generate(100, (index) => "Item: $index")); | ||
}); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
if (items == null) return Center(child: CircularProgressIndicator()); | ||
// Filter Items | ||
final filteredItems = List.from(items!) | ||
..removeWhere((name) => name.toLowerCase().contains(_filter.toLowerCase()) == false); | ||
return Column( | ||
children: [ | ||
Row( | ||
children: [ | ||
Text(widget.title, style: TextStyle(fontSize: 22)), | ||
Spacer(), | ||
Text("Search Filter:", style: TextStyle(fontSize: 16)), | ||
Expanded( | ||
child: Padding( | ||
padding: const EdgeInsets.all(8.0), | ||
child: TextFormField( | ||
onChanged: (v) { | ||
setState(() => _filter = v); | ||
}, | ||
decoration: InputDecoration(border: OutlineInputBorder()), | ||
), | ||
)), | ||
], | ||
), | ||
Expanded( | ||
child: ListView.builder( | ||
itemCount: filteredItems.length, | ||
itemBuilder: (_, index) { | ||
return OpenDetailsBtn( | ||
itemId: "$index", | ||
child: Container( | ||
width: double.infinity, | ||
padding: const EdgeInsets.all(8.0), | ||
color: (index % 2 == 0 ? Colors.grey : Colors.white).withOpacity(.1), | ||
child: Row( | ||
children: [ | ||
SizedBox( | ||
width: 100, | ||
height: 100, | ||
child: CachedNetworkImage( | ||
imageUrl: "https://source.unsplash.com/random/50x50?id=${widget.title}$index")), | ||
Text(filteredItems[index]), | ||
], | ||
), | ||
), | ||
); | ||
})), | ||
], | ||
); | ||
} | ||
} | ||
|
||
class HomePage extends SomeStatefulPage { | ||
static const String path = "home"; | ||
HomePage(String suffix) : super("Home Page, $suffix"); | ||
} | ||
|
||
/// Settings | ||
class ProfileSettings extends SomeStatefulPage { | ||
static const String path = "profile"; | ||
ProfileSettings(String suffix) : super("Profile, $suffix"); | ||
} | ||
|
||
class AlertSettings extends SomeStatefulPage { | ||
static const String path = "alerts"; | ||
AlertSettings(String suffix) : super("Alerts, $suffix"); | ||
} | ||
|
||
class BillingSettings extends SomeStatefulPage { | ||
static const String path = "billing"; | ||
BillingSettings(String suffix) : super("Billing, $suffix"); | ||
} | ||
|
||
/// Messages | ||
enum InboxType { friends, unread, archived } | ||
|
||
class InboxPage extends SomeStatefulPage { | ||
static const String friendsPath = "friends/"; | ||
static const String unreadPath = "unread/"; | ||
static const String archivedPath = "archived/"; | ||
|
||
final InboxType pageType; | ||
InboxPage(this.pageType) : super("$pageType"); | ||
} | ||
|
||
/// Compose | ||
class ComposePage extends StatefulWidget { | ||
static const String path = "compose"; | ||
@override | ||
_ComposePageState createState() => _ComposePageState(); | ||
} | ||
|
||
class _ComposePageState extends State<ComposePage> { | ||
@override | ||
Widget build(BuildContext context) { | ||
return Padding( | ||
padding: const EdgeInsets.all(8.0), | ||
child: Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
BackBtn(), | ||
Expanded( | ||
child: Column( | ||
children: [ | ||
Spacer(), | ||
Text("New Message:", style: TextStyle(fontSize: 32)), | ||
TextField( | ||
maxLines: 10, | ||
decoration: InputDecoration(border: OutlineInputBorder(borderSide: BorderSide(color: Colors.black)))), | ||
Spacer(flex: 3), | ||
], | ||
)) | ||
], | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// Details | ||
class DetailsPage extends StatefulWidget { | ||
static const String path = "details/"; | ||
|
||
DetailsPage({required this.itemId}) : super(); | ||
final String? itemId; | ||
@override | ||
_DetailsPageState createState() => _DetailsPageState(); | ||
} | ||
|
||
class _DetailsPageState extends State<DetailsPage> { | ||
// Simulates a list of items loaded from the database. | ||
// Normally this would be driven by some API call or model that exists above this view. | ||
// The point of this is to just show how we can create a history-stack from a fullscreen view. | ||
List<String> items = List.generate(100, (index) => "$index"); | ||
@override | ||
Widget build(BuildContext context) { | ||
return Padding( | ||
padding: const EdgeInsets.all(20), | ||
child: Column( | ||
children: [ | ||
Row(children: [ | ||
BackBtn(), | ||
Spacer(), | ||
CloseDetailsBtn(), | ||
]), | ||
Text("${widget.itemId}", style: TextStyle(fontSize: 32)), | ||
Row( | ||
children: [ | ||
OpenDetailsBtn(itemId: getId(-1), child: Text("<< prev item")), | ||
Spacer(), | ||
OpenDetailsBtn(itemId: getId(1), child: Text("next item >>")), | ||
], | ||
), | ||
], | ||
), | ||
); | ||
} | ||
|
||
String getId(int dir) { | ||
int currentIndex = items.indexOf(widget.itemId!); | ||
currentIndex += dir; | ||
if (currentIndex <= 0) { | ||
currentIndex = items.length - 1; | ||
} else if (currentIndex > items.length - 1) { | ||
currentIndex = 0; | ||
} | ||
return items[currentIndex]; | ||
} | ||
} |
Oops, something went wrong.