add homepage

This commit is contained in:
jyuesong
2022-01-12 15:00:55 +08:00
parent 7bfae045fb
commit 9f7164db48
15 changed files with 786 additions and 135 deletions

View File

@@ -0,0 +1,84 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'base_viewmodel.dart';
class BaseStateWidget<T extends BaseViewModel> extends ConsumerStatefulWidget {
final Widget Function(BuildContext context, T value, Widget? child) builder;
final ProviderBase<T> model;
final Widget? child;
final Function(T)? onReady;
const BaseStateWidget({
Key? key,
required this.builder,
required this.model,
this.child,
this.onReady,
}) : super(key: key);
@override
_BaseStateWidgetState<T> createState() => _BaseStateWidgetState<T>();
}
class _BaseStateWidgetState<T extends BaseViewModel> extends ConsumerState<BaseStateWidget<T>> {
@override
void initState() {
super.initState();
if (widget.onReady != null) {
widget.onReady!(ref.read<T>(widget.model));
}
}
@override
Widget build(BuildContext context) {
var viewModel = ref.watch<T>(widget.model);
if (viewModel.currentState == PageState.CONTENT) {
return widget.builder(context, viewModel, widget.child);
}
if (viewModel.currentState == PageState.LOADING) {
return Container(
alignment: Alignment.center,
child: const CupertinoActivityIndicator(),
);
}
if (viewModel.currentState == PageState.FAILED) {
return Container(
alignment: Alignment.center,
child: Text("请求失败"),
);
}
if (viewModel.currentState == PageState.EMPTY) {
return Container(
alignment: Alignment.center,
child: Text("暂无数据"),
);
}
return Container();
}
}
abstract class BaseState<T extends StatefulWidget> extends State {
late T parent;
@override
void initState() {
parent = widget as T;
super.initState();
WidgetsBinding.instance?.endOfFrame.then((_) {
firstFrameCalled();
});
}
void firstFrameCalled() {}
@override
Widget build(BuildContext context) {
return buildWidgets(context);
}
Widget buildWidgets(BuildContext context);
}

View File

@@ -0,0 +1,41 @@
import 'package:flutter/cupertino.dart';
class BaseViewModel extends ChangeNotifier {
PageState currentState = PageState.START;
void loading({bool notify = false}) {
currentState = PageState.LOADING;
if (notify) {
notifyListeners();
}
}
void success({bool notify = true}) {
currentState = PageState.CONTENT;
if (notify) {
notifyListeners();
}
}
void failed({bool notify = false}) {
currentState = PageState.FAILED;
if (notify) {
notifyListeners();
}
}
void empty({bool notify = false}) {
currentState = PageState.EMPTY;
if (notify) {
notifyListeners();
}
}
}
enum PageState {
START,
LOADING,
EMPTY,
CONTENT,
FAILED,
}

48
lib/base/ql_app_bar.dart Normal file
View File

@@ -0,0 +1,48 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class QlAppBar extends StatelessWidget with PreferredSizeWidget {
final String title;
final List<Widget>? actions;
final VoidCallback? backCall;
final bool canBack;
final Widget? backWidget;
QlAppBar({
Key? key,
required this.title,
this.actions,
this.backCall,
this.canBack = true,
this.backWidget,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget back = backWidget ??
GestureDetector(
onTap: () {
if (backCall != null) {
backCall!();
}
},
child: const Center(
child: Icon(
CupertinoIcons.left_chevron,
size: 20,
),
),
);
return AppBar(
leading: canBack ? back : null,
automaticallyImplyLeading: canBack,
title: Text(title),
centerTitle: true,
actions: [...?actions],
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

20
lib/base/theme.dart Normal file
View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
class ThemeViewModel extends ChangeNotifier {
ThemeData currentTheme = lightTheme;
void changeTheme() {
if (currentTheme == darkTheme) {
currentTheme = lightTheme;
} else {
currentTheme = darkTheme;
}
notifyListeners();
}
}
ThemeData darkTheme = ThemeData.dark().copyWith();
ThemeData lightTheme = ThemeData.light().copyWith();

View File

@@ -1,115 +1,35 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/theme.dart';
import 'module/home/home_page.dart';
void main() {
runApp(const MyApp());
runApp(
ProviderScope(
overrides: [
themeProvider,
],
child: const MyApp(),
),
);
if (Platform.isAndroid) {
SystemUiOverlayStyle style = const SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(style);
}
}
class MyApp extends StatelessWidget {
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
home: HomePage(),
);
}
}

0
lib/module/a.dart Normal file
View File

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class ConfigPage extends StatefulWidget {
const ConfigPage({Key? key}) : super(key: key);
@override
_ConfigPageState createState() => _ConfigPageState();
}
class _ConfigPageState extends State<ConfigPage> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}

16
lib/module/env/env_page.dart vendored Normal file
View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
class EnvPage extends StatefulWidget {
const EnvPage({Key? key}) : super(key: key);
@override
_EnvPageState createState() => _EnvPageState();
}
class _EnvPageState extends State<EnvPage> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}

View File

@@ -0,0 +1,117 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/ql_app_bar.dart';
import 'package:qinglong_app/base/theme.dart';
import 'package:qinglong_app/module/config/config_page.dart';
import 'package:qinglong_app/module/env/env_page.dart';
import 'package:qinglong_app/module/others/other_page.dart';
import 'package:qinglong_app/module/task/task_page.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _index = 0;
String _title = "";
List<IndexBean> titles = [];
@override
void initState() {
initTitles();
_title = titles[0].title;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: QlAppBar(
canBack: false,
title: _title,
actions: [
Consumer(builder: (context, ref, child) {
return InkWell(
onTap: () {
ref.read(themeProvider).changeTheme();
},
child: Center(child: Text("改变主题")),
);
}),
],
),
body: IndexedStack(
index: _index,
children: const [
TaskPage(),
EnvPage(),
ConfigPage(),
OtherPage(),
],
),
bottomNavigationBar: BottomNavigationBar(
items: titles
.map(
(e) => BottomNavigationBarItem(
icon: Icon(e.icon),
activeIcon: Icon(e.checkedIcon),
label: e.title,
),
)
.toList(),
currentIndex: _index,
onTap: (index) {
_index = index;
_title = titles[index].title;
setState(() {});
},
type: BottomNavigationBarType.fixed,
),
);
}
void initTitles() {
titles.clear();
titles.add(
IndexBean(
CupertinoIcons.timer,
CupertinoIcons.timer_fill,
"定时任务",
),
);
titles.add(
IndexBean(
CupertinoIcons.hammer,
CupertinoIcons.hammer_fill,
"环境变量",
),
);
titles.add(
IndexBean(
CupertinoIcons.settings,
CupertinoIcons.settings_solid,
"配置文件",
),
);
titles.add(
IndexBean(
CupertinoIcons.cube,
CupertinoIcons.cube_fill,
"其他",
),
);
}
}
class IndexBean {
IconData icon;
IconData checkedIcon;
String title;
IndexBean(this.icon, this.checkedIcon, this.title);
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class OtherPage extends StatefulWidget {
const OtherPage({Key? key}) : super(key: key);
@override
_OtherPageState createState() => _OtherPageState();
}
class _OtherPageState extends State<OtherPage> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}

View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/base_state_widget.dart';
import 'package:qinglong_app/module/task/task_viewmodel.dart';
class TaskPage extends StatefulWidget {
const TaskPage({Key? key}) : super(key: key);
@override
_TaskPageState createState() => _TaskPageState();
}
class _TaskPageState extends State<TaskPage> {
@override
Widget build(BuildContext context) {
return BaseStateWidget<TaskViewModel>(
builder: (context, model, child) {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(model.list[index]),
);
},
itemCount: model.list.length,
);
},
model: taskProvider,
onReady: (viewModel) {
viewModel.loadData();
},
);
}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/base_viewmodel.dart';
var taskProvider = ChangeNotifierProvider((ref) => TaskViewModel());
class TaskViewModel extends BaseViewModel {
List<String> list = [];
void loadData() {
loading(notify: true);
Future.delayed(Duration(seconds: 2), () {
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
list.add("sdfsf");
success();
});
}
}

171
lib/utils/sp_utils.dart Normal file
View File

@@ -0,0 +1,171 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
import 'dart:convert';
import 'package:synchronized/synchronized.dart';
class SpUtil {
static SpUtil? _singleton;
static SharedPreferences? _prefs;
static Lock _lock = Lock();
static Future<SpUtil> getInstance() async {
if (_singleton == null) {
await _lock.synchronized(() async {
if (_singleton == null) {
var singleton = SpUtil._();
await singleton._init();
_singleton = singleton;
}
});
}
return _singleton!;
}
SpUtil._();
Future _init() async {
_prefs = await SharedPreferences.getInstance();
}
/// put object.
static Future<bool>? putObject(String key, Object value) {
if (_prefs == null) return null;
return _prefs!.setString(key, json.encode(value));
}
/// get obj.
static T? getObj<T>(String key, T f(Map v), {T? defValue}) {
Map? map = getObject(key);
return map == null ? defValue : f(map);
}
/// get object.
static Map? getObject(String key) {
if (_prefs == null) return null;
String? _data = _prefs!.getString(key);
return (_data == null || _data.isEmpty) ? null : json.decode(_data);
}
/// put object list.
static Future<bool>? putObjectList(String key, List<Object> list) {
if (_prefs == null) return null;
List<String> _dataList = list.map((value) {
return json.encode(value);
}).toList();
return _prefs!.setStringList(key, _dataList);
}
/// get obj list.
static List<T>? getObjList<T>(String key, T f(Map v), {List<T> defValue = const []}) {
List<Map>? dataList = getObjectList(key);
List<T>? list = dataList?.map((value) {
return f(value);
}).toList();
return list ?? defValue;
}
/// get object list.
static List<Map>? getObjectList(String key) {
if (_prefs == null) return null;
List<String>? dataLis = _prefs!.getStringList(key);
return dataLis?.map((value) {
Map _dataMap = json.decode(value);
return _dataMap;
}).toList();
}
/// get string.
static String getString(String key, {String defValue = ''}) {
if (_prefs == null) return defValue;
return _prefs!.getString(key) ?? defValue;
}
/// put string.
static Future<bool>? putString(String key, String value) {
if (_prefs == null) return null;
return _prefs!.setString(key, value);
}
/// get bool.
static bool getBool(String key, {bool defValue = false}) {
if (_prefs == null) return defValue;
return _prefs!.getBool(key) ?? defValue;
}
/// put bool.
static Future<bool>? putBool(String key, bool value) {
if (_prefs == null) return null;
return _prefs!.setBool(key, value);
}
/// get int.
static int getInt(String key, {int defValue = 0}) {
if (_prefs == null) return defValue;
return _prefs!.getInt(key) ?? defValue;
}
/// put int.
static Future<bool>? putInt(String key, int value) {
if (_prefs == null) return null;
return _prefs!.setInt(key, value);
}
/// get double.
static double getDouble(String key, {double defValue = 0.0}) {
if (_prefs == null) return defValue;
return _prefs!.getDouble(key) ?? defValue;
}
/// put double.
static Future<bool>? putDouble(String key, double value) {
if (_prefs == null) return null;
return _prefs!.setDouble(key, value);
}
/// get string list.
static List<String> getStringList(String key, {List<String> defValue = const []}) {
if (_prefs == null) return defValue;
return _prefs!.getStringList(key) ?? defValue;
}
/// put string list.
static Future<bool>? putStringList(String key, List<String> value) {
if (_prefs == null) return null;
return _prefs!.setStringList(key, value);
}
/// get dynamic.
static dynamic getDynamic(String key, {Object? defValue}) {
if (_prefs == null) return defValue;
return _prefs!.get(key) ?? defValue;
}
/// have key.
static bool haveKey(String key) {
if (_prefs == null) return false;
return _prefs!.getKeys().contains(key);
}
/// get keys.
static Set<String>? getKeys() {
if (_prefs == null) return null;
return _prefs!.getKeys();
}
/// remove.
static Future<bool>? remove(String key) {
if (_prefs == null) return null;
return _prefs!.remove(key);
}
/// clear.
static Future<bool>? clear() {
if (_prefs == null) return null;
return _prefs!.clear();
}
///Sp is initialized.
static bool isInitialized() {
return _prefs != null;
}
}