mirror of
https://github.com/simon-ding/polaris.git
synced 2026-06-09 11:39:46 +08:00
WIP: init wizard
This commit is contained in:
190
ui/lib/init_wizard.dart
Normal file
190
ui/lib/init_wizard.dart
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:ui/settings/prowlarr.dart';
|
||||||
|
import 'package:ui/widgets/widgets.dart';
|
||||||
|
|
||||||
|
class InitWizard extends ConsumerStatefulWidget {
|
||||||
|
const InitWizard({super.key});
|
||||||
|
static final String route = "/init_wizard";
|
||||||
|
@override
|
||||||
|
ConsumerState<ConsumerStatefulWidget> createState() {
|
||||||
|
return _InitWizardState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InitWizardState extends ConsumerState<InitWizard> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SelectionArea(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(50),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
"Polaris 影视追踪下载",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 30,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(left: 10, top: 30, bottom: 30),
|
||||||
|
child: Text(
|
||||||
|
"设置向导",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
tmdbSetting(),
|
||||||
|
downloaderSetting(),
|
||||||
|
indexerSetting(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget tmdbSetting() {
|
||||||
|
return ExpansionTile(
|
||||||
|
title: Text(
|
||||||
|
"第一步:TMDB设置",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
childrenPadding: EdgeInsets.only(left: 100, right: 20),
|
||||||
|
initiallyExpanded: true,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text("TMDB API Key 设置,用来获取各种影视的信息,API Key获取方式参考官网"),
|
||||||
|
),
|
||||||
|
FormBuilder(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: "tmdb",
|
||||||
|
decoration: InputDecoration(labelText: "TMDB API Key"),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: ElevatedButton(onPressed: null, child: Text("保存")),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget indexerSetting() {
|
||||||
|
return ExpansionTile(
|
||||||
|
initiallyExpanded: true,
|
||||||
|
childrenPadding: EdgeInsets.only(left: 100, right: 20),
|
||||||
|
title: Text(
|
||||||
|
"第三步:Prowlarr设置",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
children: [ProwlarrSettingPage()],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget downloaderSetting() {
|
||||||
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
var _enableAuth = false;
|
||||||
|
String selectImpl = "transmission";
|
||||||
|
|
||||||
|
return ExpansionTile(
|
||||||
|
childrenPadding: EdgeInsets.only(left: 100, right: 20),
|
||||||
|
initiallyExpanded: true,
|
||||||
|
title: Text("第二步:下载客户端", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
children: [
|
||||||
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return FormBuilder(
|
||||||
|
key: _formKey,
|
||||||
|
initialValue: {
|
||||||
|
"name": "client.name",
|
||||||
|
"url": "client.url",
|
||||||
|
"user": "client.user",
|
||||||
|
"password": "client.password",
|
||||||
|
"impl": selectImpl,
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
FormBuilderDropdown<String>(
|
||||||
|
name: "impl",
|
||||||
|
decoration: const InputDecoration(labelText: "类型"),
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: "transmission", child: Text("Transmission")),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: "qbittorrent", child: Text("qBittorrent")),
|
||||||
|
],
|
||||||
|
validator: FormBuilderValidators.required(),
|
||||||
|
),
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: "name",
|
||||||
|
decoration: const InputDecoration(labelText: "名称"),
|
||||||
|
validator: FormBuilderValidators.required(),
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction),
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: "url",
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "地址", hintText: "http://127.0.0.1:9091"),
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
validator: FormBuilderValidators.required(),
|
||||||
|
),
|
||||||
|
StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
FormBuilderSwitch(
|
||||||
|
name: "auth",
|
||||||
|
title: const Text("需要认证"),
|
||||||
|
initialValue: _enableAuth,
|
||||||
|
onChanged: (v) {
|
||||||
|
setState(() {
|
||||||
|
_enableAuth = v!;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
_enableAuth
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: "user",
|
||||||
|
decoration:
|
||||||
|
Commons.requiredTextFieldStyle(
|
||||||
|
text: "用户"),
|
||||||
|
validator:
|
||||||
|
FormBuilderValidators.required(),
|
||||||
|
autovalidateMode:
|
||||||
|
AutovalidateMode.onUserInteraction),
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: "password",
|
||||||
|
decoration:
|
||||||
|
Commons.requiredTextFieldStyle(
|
||||||
|
text: "密码"),
|
||||||
|
validator:
|
||||||
|
FormBuilderValidators.required(),
|
||||||
|
obscureText: true,
|
||||||
|
autovalidateMode:
|
||||||
|
AutovalidateMode.onUserInteraction),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Container()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
177
ui/lib/main.dart
177
ui/lib/main.dart
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:ui/activity.dart';
|
import 'package:ui/activity.dart';
|
||||||
import 'package:ui/calendar.dart';
|
import 'package:ui/calendar.dart';
|
||||||
|
import 'package:ui/init_wizard.dart';
|
||||||
import 'package:ui/login_page.dart';
|
import 'package:ui/login_page.dart';
|
||||||
import 'package:ui/movie_watchlist.dart';
|
import 'package:ui/movie_watchlist.dart';
|
||||||
import 'package:ui/providers/APIs.dart';
|
import 'package:ui/providers/APIs.dart';
|
||||||
@@ -124,9 +125,17 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||||||
initialLocation: WelcomePage.routeTv,
|
initialLocation: WelcomePage.routeTv,
|
||||||
routes: [
|
routes: [
|
||||||
shellRoute,
|
shellRoute,
|
||||||
|
GoRoute(
|
||||||
|
path: "/",
|
||||||
|
redirect: (context, state) => WelcomePage.routeTv,
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: LoginScreen.route,
|
path: LoginScreen.route,
|
||||||
builder: (context, state) => const LoginScreen(),
|
builder: (context, state) => const LoginScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: InitWizard.route,
|
||||||
|
builder: (context, state) => const InitWizard(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -171,88 +180,7 @@ class _MainSkeletonState extends State<MainSkeleton> {
|
|||||||
var padding = isSmallScreen(context) ? 5.0 : 20.0;
|
var padding = isSmallScreen(context) ? 5.0 : 20.0;
|
||||||
return AdaptiveScaffold(
|
return AdaptiveScaffold(
|
||||||
appBarBreakpoint: Breakpoints.standard,
|
appBarBreakpoint: Breakpoints.standard,
|
||||||
appBar: AppBar(
|
appBar: appBar(),
|
||||||
// TRY THIS: Try changing the color here to a specific color (to
|
|
||||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
|
||||||
// change color while the other colors stay the same.
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
||||||
// 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.
|
|
||||||
leading: Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => context.go(WelcomePage.routeTv),
|
|
||||||
child: const Text(
|
|
||||||
"Polaris",
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
style: TextStyle(fontSize: 28),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leadingWidth: isSmallScreen(context) ? 0 : 190,
|
|
||||||
title: Container(
|
|
||||||
alignment: Alignment.bottomLeft,
|
|
||||||
child: SearchAnchor(
|
|
||||||
builder: (BuildContext context, SearchController controller) {
|
|
||||||
return Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 250, maxHeight: 40),
|
|
||||||
child: Opacity(
|
|
||||||
opacity: 0.8,
|
|
||||||
child: SearchBar(
|
|
||||||
hintText: "在此搜索...",
|
|
||||||
leading: const Icon(Icons.search),
|
|
||||||
controller: controller,
|
|
||||||
shadowColor: WidgetStateColor.transparent,
|
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
|
||||||
Theme.of(context).colorScheme.primaryContainer),
|
|
||||||
onSubmitted: (value) => context.go(Uri(
|
|
||||||
path: SearchPage.route,
|
|
||||||
queryParameters: {'query': value}).toString()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, suggestionsBuilder:
|
|
||||||
(BuildContext context, SearchController controller) {
|
|
||||||
return [Text("dadada")];
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
|
|
||||||
actions: [
|
|
||||||
// IconButton(
|
|
||||||
// onPressed: () => showCalendar(context),
|
|
||||||
// icon: Icon(Icons.calendar_month)),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => showDonate(context),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.favorite_rounded,
|
|
||||||
color: Colors.red,
|
|
||||||
)),
|
|
||||||
|
|
||||||
MenuAnchor(
|
|
||||||
menuChildren: [
|
|
||||||
MenuItemButton(
|
|
||||||
leadingIcon: const Icon(Icons.exit_to_app),
|
|
||||||
child: const Text("登出"),
|
|
||||||
onPressed: () async {
|
|
||||||
await APIs.logout();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
builder: (context, controller, child) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (controller.isOpen) {
|
|
||||||
controller.close();
|
|
||||||
} else {
|
|
||||||
controller.open();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.account_circle),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
useDrawer: false,
|
useDrawer: false,
|
||||||
selectedIndex: widget.body.currentIndex,
|
selectedIndex: widget.body.currentIndex,
|
||||||
onSelectedIndexChange: (p0) => widget.body
|
onSelectedIndexChange: (p0) => widget.body
|
||||||
@@ -314,4 +242,89 @@ class _MainSkeletonState extends State<MainSkeleton> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppBar appBar() {
|
||||||
|
return AppBar(
|
||||||
|
// TRY THIS: Try changing the color here to a specific color (to
|
||||||
|
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||||
|
// change color while the other colors stay the same.
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
|
// 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.
|
||||||
|
leading: Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => context.go(WelcomePage.routeTv),
|
||||||
|
child: const Text(
|
||||||
|
"Polaris",
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
style: TextStyle(fontSize: 28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leadingWidth: isSmallScreen(context) ? 0 : 190,
|
||||||
|
title: Container(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: SearchAnchor(
|
||||||
|
builder: (BuildContext context, SearchController controller) {
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 250, maxHeight: 40),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.8,
|
||||||
|
child: SearchBar(
|
||||||
|
hintText: "在此搜索...",
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
controller: controller,
|
||||||
|
shadowColor: WidgetStateColor.transparent,
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context).colorScheme.primaryContainer),
|
||||||
|
onSubmitted: (value) => context.go(Uri(
|
||||||
|
path: SearchPage.route,
|
||||||
|
queryParameters: {'query': value}).toString()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, suggestionsBuilder:
|
||||||
|
(BuildContext context, SearchController controller) {
|
||||||
|
return [Text("dadada")];
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () => showCalendar(context),
|
||||||
|
// icon: Icon(Icons.calendar_month)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => showDonate(context),
|
||||||
|
icon: Icon(
|
||||||
|
Icons.favorite_rounded,
|
||||||
|
color: Colors.red,
|
||||||
|
)),
|
||||||
|
|
||||||
|
MenuAnchor(
|
||||||
|
menuChildren: [
|
||||||
|
MenuItemButton(
|
||||||
|
leadingIcon: const Icon(Icons.exit_to_app),
|
||||||
|
child: const Text("登出"),
|
||||||
|
onPressed: () async {
|
||||||
|
await APIs.logout();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
builder: (context, controller, child) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (controller.isOpen) {
|
||||||
|
controller.close();
|
||||||
|
} else {
|
||||||
|
controller.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.account_circle),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ class ProwlarrSettingState extends ConsumerState<ProwlarrSettingPage> {
|
|||||||
FormBuilderSwitch(
|
FormBuilderSwitch(
|
||||||
name: "disabled",
|
name: "disabled",
|
||||||
title: const Text("禁用 Prowlarr"),
|
title: const Text("禁用 Prowlarr"),
|
||||||
decoration: InputDecoration(icon: Icon(Icons.do_not_disturb)),
|
decoration:
|
||||||
|
InputDecoration(icon: Icon(Icons.do_not_disturb)),
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
Reference in New Issue
Block a user