This commit is contained in:
cxc
2023-02-08 17:20:16 +08:00
parent 06ff4a41f4
commit 53ff2fc59b
23 changed files with 458 additions and 104 deletions

View File

@ -3,7 +3,7 @@
<application
android:label="momo"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,6 +1,8 @@
flutter_icons:
android: "launcher_icon"
ios: true
image_path: "assets/icon/icon.jpeg"
min_sdk_android: 21 # android min sdk min:16, default 21
windows:
generate: true
image_path: "assets/icon/icon.jpeg"

View File

@ -1,10 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart';
// import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:momo/fluent/login.dart';
import 'package:momo/fluent/router.dart';
import 'package:momo/provider/token.dart';
import 'package:shared_preferences/shared_preferences.dart';
class MyFluentApp extends ConsumerWidget {
@ -12,10 +8,10 @@ class MyFluentApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final String token = ref.watch(tokenProvider);
// final String token = ref.watch(tokenProvider);
Future<String?> loadToken() async {
final prefs = await SharedPreferences.getInstance();
String? tk = await prefs.getString("token");
String? tk = prefs.getString("token");
return tk;
}

View File

@ -6,6 +6,8 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:momo/models/login_resp.dart';
import 'package:momo/provider/token.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginPage extends StatelessWidget {
@ -59,21 +61,25 @@ class _LoginFormState extends State<LoginForm> {
if (usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty) {
http
.post(Uri.parse("http://localhost:8080/user/login"),
.post(Uri.parse("http://192.168.110.156:8080/user/login"),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"username": usernameController.text,
"password": passwordController.text
}))
.then((resp) {
print(resp.body);
if (resp.statusCode == HttpStatus.ok) {
LoginResp loginResp =
LoginResp.fromJson(jsonDecode(resp.body));
SharedPreferences.getInstance().then((prefs) {
return prefs.setString("token", loginResp.token);
}).then((value) {
print(loginResp.token);
// Provider.of<Secret>(context).setToken(loginResp.token);
context.go("/");
});
// SharedPreferences.getInstance().then((prefs) {
// prefs.setString("token", loginResp.token);
// });
// .then((value) {
// });
}
});
}

View File

@ -2,9 +2,13 @@ import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:momo/material/app.dart';
import 'package:momo/provider/token.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
import 'package:tray_manager/tray_manager.dart';
// import 'package:tray_manager/tray_manager.dart';
void main() async {
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
@ -24,14 +28,38 @@ void main() async {
await windowManager.focus();
});
}
runApp(const ProviderScope(child: MyApp()));
final prefs = await SharedPreferences.getInstance();
String? tk = prefs.getString("token");
final container = ProviderContainer();
container.read(tokenProvider.notifier).setToken(tk);
runApp(
UncontrolledProviderScope(
container: container,
child: const MyApp(),
),
);
// runApp((child: MyApp(token: tk)));
// runApp(MultiProvider(
// providers: [
// ChangeNotifierProvider(
// create: (context) => Secret(),
// ),
// ],
// child: MyApp(
// token: tk,
// ),
// ));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
const MyApp({super.key, this.token});
final String? token;
@override
Widget build(BuildContext context) {
// Provider.of<Secret>(context, listen: false).setToken(token);
return const MyMaterialApp();
}
}

View File

@ -1,49 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:momo/material/router.dart';
import 'package:momo/provider/token.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:provider/provider.dart';
class MyMaterialApp extends ConsumerWidget {
const MyMaterialApp({Key? key}) : super(key: key);
Future<String?> loadToken() async {
final prefs = await SharedPreferences.getInstance();
String? tk = await prefs.getString("token");
String? tk = prefs.getString("token");
return tk;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return FutureBuilder(
future: loadToken(),
builder: (BuildContext context, AsyncSnapshot<String?> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
String? token = snapshot.data;
if (token != null) {
// ref
// .watch(
// tokenProvider.notifier,
// )
// .setToken(token);
}
String? token = ref.watch(tokenProvider);
MyMaterialRouterConfig myMaterialRouterConfig =
MyMaterialRouterConfig(token);
return MaterialApp.router(
routerConfig: myMaterialRouterConfig.router,
theme: ThemeData(
useMaterial3: true, scaffoldBackgroundColor: Colors.white),
debugShowCheckedModeBanner: false,
theme:
ThemeData(useMaterial3: true, scaffoldBackgroundColor: Colors.white),
);
} else {
return MaterialApp(
theme: ThemeData(
useMaterial3: true, scaffoldBackgroundColor: Colors.white),
home: const Center(
child: CircularProgressIndicator(),
),
);
}
});
// return FutureBuilder(
// future: loadToken(),
// builder: (BuildContext context, AsyncSnapshot<String?> snapshot) {
// if (snapshot.connectionState == ConnectionState.done) {
// String? token = snapshot.data;
// if (token != null) {
// // ref
// // .watch(
// // tokenProvider.notifier,
// // )
// // .setToken(token);
// }
// MyMaterialRouterConfig myMaterialRouterConfig =
// MyMaterialRouterConfig(token);
//
// return MaterialApp.router(
// routerConfig: myMaterialRouterConfig.router,
// theme: ThemeData(
// useMaterial3: true, scaffoldBackgroundColor: Colors.white),
// );
// } else {
// return MaterialApp(
// theme: ThemeData(
// useMaterial3: true, scaffoldBackgroundColor: Colors.white),
// home: const Center(
// child: CircularProgressIndicator(),
// ),
// );
// }
// });
}
}

View File

@ -1,8 +1,18 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:momo/models/image_list_resp.dart';
import 'package:momo/models/image_resp.dart';
import 'package:momo/provider/token.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:go_router/go_router.dart';
// import 'package:momo/provider/token.dart';
// import 'package:provider/provider.dart';
class Gallery extends ConsumerStatefulWidget {
const Gallery({Key? key}) : super(key: key);
@ -11,17 +21,47 @@ class Gallery extends ConsumerStatefulWidget {
}
class _GalleryState extends ConsumerState<Gallery> {
Future<dynamic> loadImages(tk) async {
http.Response resp = await http.get(
Uri.parse("http://192.168.110.156:8080/image/history"),
headers: {"Authorization": tk});
return resp.body;
}
List<ImageResp> imageList = [];
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
String token = ref.watch(tokenProvider);
print(token);
return Center(
child: ElevatedButton(
onPressed: () {
// ref.watch(tokenProvider.notifier).removeToken();
context.go("/login");
String? tk = ref.watch(tokenProvider);
if (tk != null) {
loadImages(tk).then((bd) {
if (mounted) {
setState(() {
imageList = ImageListResp.fromJson(jsonDecode(bd)).list;
});
}
});
}
return GridView.builder(
itemCount: imageList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: MediaQuery.of(context).size.width > 640 ? 5 : 3),
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
context.go("/detail");
},
child: Text("exit")),
child: Image.network(
"http://192.168.110.156:8080/image/thumbnail/${imageList[index].file_path}",
fit: BoxFit.cover,
),
);
});
}
}

View File

@ -1,19 +1,32 @@
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:momo/material/gallery.dart';
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import 'package:momo/provider/token.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
class HomePage extends ConsumerStatefulWidget {
const HomePage({Key? key, required this.content}) : super(key: key);
final Widget content;
@override
State<HomePage> createState() => _HomePageState();
ConsumerState<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
class _HomePageState extends ConsumerState<HomePage> {
int selectedIndex = 0;
final tabList = [
{
"path": "/",
},
{"path": "/profile"}
];
@override
Widget build(BuildContext context) {
String? token = ref.watch(tokenProvider);
return Scaffold(
appBar: AppBar(
title: const Text(
@ -22,6 +35,39 @@ class _HomePageState extends State<HomePage> {
),
elevation: 10,
),
floatingActionButton: selectedIndex == 0
? FloatingActionButton(
onPressed: () async {
if (token == null) {
return;
}
FilePickerResult? result =
await FilePicker.platform.pickFiles(type: FileType.image);
if (result != null) {
String? filePath = result.files.first.path;
if (filePath == null) {
return;
}
Dio dio = Dio();
String? mimeType = lookupMimeType(filePath);
FormData data = FormData.fromMap({
"pic": await MultipartFile.fromFile(filePath,
filename: result.files.first.name,
contentType: MediaType(mimeType!.split("/").first,
mimeType.split("/").last))
});
Response resp = await dio.post(
"http://192.168.110.156:8080/image/upload",
data: data,
options: Options(headers: {"Authorization": token}));
} else {}
// context.go("/login");
},
child: const Icon(Icons.add),
)
: null,
body: Row(
children: [
MediaQuery.of(context).size.width > 640
@ -39,6 +85,7 @@ class _HomePageState extends State<HomePage> {
setState(() {
selectedIndex = idx;
});
context.go(tabList[idx]["path"] ?? "");
},
destinations: const [
NavigationRailDestination(
@ -51,7 +98,7 @@ class _HomePageState extends State<HomePage> {
: const SizedBox(
width: 0,
),
Expanded(child: pageList[selectedIndex])
Expanded(child: widget.content)
],
),
bottomNavigationBar: MediaQuery.of(context).size.width <= 640
@ -72,10 +119,3 @@ class _HomePageState extends State<HomePage> {
);
}
}
List<Widget> pageList = [
Gallery(),
Scaffold(
body: Placeholder(),
)
];

View File

@ -3,10 +3,13 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:momo/models/login_resp.dart';
import 'package:momo/provider/token.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginPage extends StatelessWidget {
@ -75,24 +78,24 @@ class _LoginFormState extends ConsumerState<LoginForm> {
onPressed: () async {
if (usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty) {
http.Response resp = await http.post(
Uri.parse("http://localhost:8080/user/login"),
http
.post(Uri.parse("http://192.168.110.156:8080/user/login"),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"username": usernameController.text,
"password": passwordController.text
}));
}))
.then((resp) {
if (resp.statusCode == HttpStatus.ok) {
LoginResp loginResp =
LoginResp.fromJson(jsonDecode(resp.body));
SharedPreferences prefs =
await SharedPreferences.getInstance();
// ref.watch(tokenProvider.notifier).setToken(loginResp.token);
prefs.setString("token", loginResp.token).then((value) {
context.go("/");
});
ref.watch(tokenProvider.notifier).setToken(loginResp.token);
// context.go("/");
// });
}
}).catchError((err) {
print(err.toString());
});
}
}),
],

18
lib/material/profile.dart Normal file
View File

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:momo/provider/token.dart';
class Profile extends ConsumerWidget {
const Profile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Center(
child: ElevatedButton(
onPressed: () {
ref.watch(tokenProvider.notifier).removeToken();
},
child: const Text('quit')),
);
}
}

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:momo/material/gallery.dart';
import 'package:momo/material/home.dart';
import 'package:momo/material/login.dart';
import 'package:momo/material/profile.dart';
class MyMaterialRouterConfig {
late GoRouter router;
@ -9,23 +11,65 @@ class MyMaterialRouterConfig {
MyMaterialRouterConfig(String? token) {
router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomePage();
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return HomePage(
content: child,
);
},
routes: [
GoRoute(
path: "/",
pageBuilder: (BuildContext context, GoRouterState state) =>
const NoTransitionPage(child: Gallery()),
redirect: (BuildContext context, GoRouterState state) {
if (token == null || token.isEmpty) {
return '/login';
}
return null;
}),
GoRoute(
path: "/profile",
pageBuilder: (BuildContext context, GoRouterState state) =>
const NoTransitionPage(child: Profile())),
GoRoute(
path: "/detail",
pageBuilder: (BuildContext context, GoRouterState state) =>
const NoTransitionPage(
child: Center(
child: Text("detail"),
)))
],
),
GoRoute(
path: "/login",
builder: (BuildContext context, GoRouterState state) {
return const LoginPage();
},
)
redirect: (BuildContext context, GoRouterState state) {
if (token != null && token.isNotEmpty) {
return '/';
}
return null;
}),
// ShellRoute(
// builder: (BuildContext context, GoRouterState state, Widget child) {
// return Scaffold(
// appBar: AppBar(
// title: Text("nest"),
// ),
// body: child,
// );
// },
// routes: [
// GoRoute(
// path: '/',
// builder: (BuildContext context, GoRouterState state) {
// return Center(
// child: Text("login"),
// );
// })
// ]),
],
);
}

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:momo/models/image_resp.dart';
part 'image_list_resp.g.dart';
@JsonSerializable()
class ImageListResp {
List<ImageResp> list;
int total;
ImageListResp(this.list, this.total);
factory ImageListResp.fromJson(Map<String, dynamic> json) =>
_$ImageListRespFromJson(json);
Map<String, dynamic> toJson() => _$ImageListRespToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'image_list_resp.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ImageListResp _$ImageListRespFromJson(Map<String, dynamic> json) =>
ImageListResp(
(json['list'] as List<dynamic>)
.map((e) => ImageResp.fromJson(e as Map<String, dynamic>))
.toList(),
json['total'] as int,
);
Map<String, dynamic> _$ImageListRespToJson(ImageListResp instance) =>
<String, dynamic>{
'list': instance.list,
'total': instance.total,
};

View File

@ -0,0 +1,30 @@
import 'dart:ffi';
import 'package:json_annotation/json_annotation.dart';
part 'image_resp.g.dart';
@JsonSerializable()
class ImageResp {
int id;
String file_name;
String file_path;
int upload_time;
int size;
int width;
int height;
ImageResp(this.id, this.file_name, this.file_path, this.upload_time,
this.size, this.width, this.height);
/// A necessary factory constructor for creating a new Configs instance
/// from a map. Pass the map to the generated `_$ConfigsFromJson()` constructor.
/// The constructor is named after the source class, in this case, Configs.
factory ImageResp.fromJson(Map<String, dynamic> json) =>
_$ImageRespFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$ImageRespToJson`.
Map<String, dynamic> toJson() => _$ImageRespToJson(this);
}

View File

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'image_resp.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ImageResp _$ImageRespFromJson(Map<String, dynamic> json) => ImageResp(
json['id'] as int,
json['file_name'] as String,
json['file_path'] as String,
json['upload_time'] as int,
json['size'] as int,
json['width'] as int,
json['height'] as int,
);
Map<String, dynamic> _$ImageRespToJson(ImageResp instance) => <String, dynamic>{
'id': instance.id,
'file_name': instance.file_name,
'file_path': instance.file_path,
'upload_time': instance.upload_time,
'size': instance.size,
'width': instance.width,
'height': instance.height,
};

View File

@ -1,16 +1,16 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
class TokenNotifier extends Notifier<String> {
class TokenNotifier extends Notifier<String?> {
@override
String build() {
String? build() {
return "";
}
setToken(String token) {
setToken(String? token) {
state = token;
SharedPreferences.getInstance().then((prefs) {
prefs.setString("token", token);
prefs.setString("token", token ?? "");
});
}
@ -23,4 +23,28 @@ class TokenNotifier extends Notifier<String> {
}
final tokenProvider =
NotifierProvider<TokenNotifier, String>(() => TokenNotifier());
NotifierProvider<TokenNotifier, String?>(() => TokenNotifier());
// import 'package:fluent_ui/fluent_ui.dart';
// import 'package:fluent_ui/fluent_ui.dart';
// import 'package:shared_preferences/shared_preferences.dart';
//
// class Secret extends ChangeNotifier {
// String? token;
//
// void setToken(String? data) {
// token = data;
// SharedPreferences.getInstance().then((prefs) {
// prefs.setString("token", data ?? "");
// });
// // notifyListeners();
// // notifyListeners();
// }
//
// void removeToken() {
// token = "";
// SharedPreferences.getInstance().then((prefs) {
// prefs.setString("token", "");
// });
// }
// }

View File

@ -201,6 +201,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "2.2.4"
dio:
dependency: "direct main"
description:
name: dio
sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "4.0.6"
fake_async:
dependency: transitive
description:
@ -225,6 +233,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "6.1.4"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: d090ae03df98b0247b82e5928f44d1b959867049d18d73635e2e0bc3f49542b9
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "5.2.5"
fixnum:
dependency: transitive
description:
@ -275,6 +291,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "2.0.7"
flutter_riverpod:
dependency: "direct main"
description:
@ -350,7 +374,7 @@ packages:
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
dependency: "direct main"
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
@ -454,7 +478,7 @@ packages:
source: hosted
version: "1.8.0"
mime:
dependency: transitive
dependency: "direct main"
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
@ -469,6 +493,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "3.7.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.0.0"
package_config:
dependency: transitive
description:
@ -557,6 +589,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "4.2.4"
provider:
dependency: "direct main"
description:
name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "6.0.5"
pub_semver:
dependency: transitive
description:

View File

@ -46,6 +46,11 @@ dependencies:
flutter_riverpod: ^2.1.3
window_manager: ^0.3.0
tray_manager: ^0.2.0
provider: ^6.0.5
file_picker: ^5.2.5
dio: ^4.0.6
http_parser: ^4.0.2
mime: ^1.0.4
dev_dependencies:
flutter_test: