feat: 接口服务、主页页面开发

This commit is contained in:
liyi 2025-09-01 10:24:46 +08:00
parent 422e6a0291
commit 4e8e860810
14 changed files with 385 additions and 41 deletions

View File

@ -1,29 +1,55 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:label="@string/app_name" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2"/>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
<!--允许访问网络,必选权限-->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 读权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

3
lib/api/api_path.dart Normal file
View File

@ -0,0 +1,3 @@
class ApiPath {
static const String login = "/auth/login";
}

51
lib/api/api_response.dart Normal file
View File

@ -0,0 +1,51 @@
class ApiResponse<T> {
final bool success;
final T? data;
final String? message;
final int? statusCode;
//
const ApiResponse({
required this.success,
this.data,
this.message,
this.statusCode,
});
//
factory ApiResponse.success(T data,
{String message = 'Success', int? statusCode}) {
return ApiResponse<T>(
success: true,
data: data,
message: message,
statusCode: statusCode,
);
}
//
factory ApiResponse.error(String message, {int? statusCode, T? data}) {
return ApiResponse<T>(
success: false,
message: message,
statusCode: statusCode,
data: data, //
);
}
//
factory ApiResponse.loading() {
return ApiResponse<T>(
success: false,
message: 'Loading...',
);
}
@override
List<Object?> get props => [success, data, message, statusCode];
@override
String toString() {
return 'ApiResponse(success: $success, message: $message, data: $data, statusCode: $statusCode)';
}
}

View File

@ -0,0 +1,85 @@
// api/service/user_api_service.dart
import 'package:dio/dio.dart' as dioAlias;
import 'package:starwork_flutter/api/api_response.dart';
import 'package:starwork_flutter/flavors.dart';
class BaseApiService {
final dioAlias.Dio dio = dioAlias.Dio();
BaseApiService() {
dio.options.baseUrl = F.apiHost;
dio.options.connectTimeout = const Duration(seconds: 30);
dio.options.receiveTimeout = const Duration(seconds: 30);
// token
}
///
Future<ApiResponse<T>> makeRequest<T>({
required String path,
String method = 'GET',
dynamic data,
Map<String, dynamic>? queryParameters,
required T Function(dynamic) fromJson,
}) async {
try {
dioAlias.Response response;
switch (method.toUpperCase()) {
case 'GET':
response = await dio.get(path, queryParameters: queryParameters);
break;
case 'POST':
response = await dio.post(path,
data: data, queryParameters: queryParameters);
break;
case 'PUT':
response =
await dio.put(path, data: data, queryParameters: queryParameters);
break;
case 'DELETE':
response = await dio.delete(path,
data: data, queryParameters: queryParameters);
break;
case 'PATCH':
response = await dio.patch(path,
data: data, queryParameters: queryParameters);
break;
default:
return ApiResponse.error('Unsupported method: $method');
}
if (response.statusCode! >= 200 && response.statusCode! < 300) {
final parsedData = fromJson(response.data);
return ApiResponse.success(
parsedData,
statusCode: response.statusCode,
message: 'Success',
);
} else {
return ApiResponse.error(
response.statusMessage ?? 'Request failed',
statusCode: response.statusCode,
);
}
} on dioAlias.DioException catch (e) {
String message = 'Unknown error';
int? statusCode = e.response?.statusCode;
if (e.type == dioAlias.DioExceptionType.connectionTimeout) {
message = 'Connection timeout';
} else if (e.type == dioAlias.DioExceptionType.receiveTimeout) {
message = 'Server timeout';
} else if (e.type == dioAlias.DioExceptionType.badResponse) {
message = e.response?.statusMessage ?? 'Bad response';
} else if (e.type == dioAlias.DioExceptionType.connectionError) {
message = 'Network not available';
} else {
message = e.message ?? 'Something went wrong';
}
return ApiResponse.error(message, statusCode: statusCode);
} catch (e) {
return ApiResponse.error('Unexpected error: $e');
}
}
}

View File

@ -0,0 +1,24 @@
class UserModel {
final int id;
final String name;
final String email;
UserModel({required this.id, required this.name, required this.email});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}

View File

@ -0,0 +1,18 @@
import 'package:get/get.dart';
import 'package:starwork_flutter/api/api_response.dart';
import 'package:starwork_flutter/api/base_api_service.dart';
import 'package:starwork_flutter/api/model/user/user_model.dart';
class UserApiService {
final BaseApiService _api = BaseApiService();
Future<ApiResponse<UserModel>> login(Map<String, dynamic> userData) {
return _api.makeRequest(
//
path: '/login',
method: 'POST',
data: userData,
fromJson: (data) => UserModel.fromJson(data),
);
}
}

View File

@ -42,7 +42,7 @@ class App extends StatelessWidget {
fallbackLocale: const Locale('zh', 'CN'),
// fallback 使
getPages: AppPages.pages,
initialRoute: AppRoutes.login,
initialRoute: AppRoutes.main,
debugShowCheckedModeBanner: false,
);
},

View File

@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:starwork_flutter/api/base_api_service.dart';
import 'package:starwork_flutter/api/service/user_api_service.dart';
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
import 'package:starwork_flutter/i18n/app_i18n.dart';
@ -15,6 +17,8 @@ class AppInitialization {
setSystemStatusBar();
// SharedPreferences
await SharedPreferencesUtils.init();
// api服务
Get.put(BaseApiService(),permanent: true);
}
static void setSystemStatusBar() {

View File

@ -0,0 +1,23 @@
import 'package:permission_handler/permission_handler.dart';
class AppPermission {
//
static Future<bool> checkPermission({required Permission permission}) async {
var status = await permission.status;
return status == PermissionStatus.granted;
}
//
static Future<bool> checkPermissions({
required List<Permission> permissions,
}) async {
if (permissions.isEmpty) return false;
Map<Permission, PermissionStatus> statuses = await permissions.request();
for (var status in statuses.values) {
if (status != PermissionStatus.granted) {
return false;
}
}
return true;
}
}

View File

@ -1,5 +1,16 @@
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:starwork_flutter/base/app_permission.dart';
import 'package:starwork_flutter/base/base_controller.dart';
class HomeController extends BaseController {
final isOpenNotificationPermission = false.obs;
@override
void onInit() async {
super.onInit();
isOpenNotificationPermission.value = await AppPermission.checkPermission(
permission: Permission.notification,
);
}
}

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:starwork_flutter/base/app_permission.dart';
import 'home_controller.dart';
@ -9,20 +12,76 @@ class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('HomeView'),
centerTitle: true,
body: SafeArea(
child: Container(
width: 1.sw,
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
children: [
_buildPageHead(),
_buildSystemNotificationPermissionRow(),
],
),
),
),
body: Center(
child: ElevatedButton(
onPressed: () {
if (Get.locale?.languageCode == 'zh') {
Get.updateLocale(const Locale('en', 'US'));
} else {
Get.updateLocale(const Locale('zh', 'CN'));
}
},
child: Text('切换成英语${'路由'.tr}')),
);
}
_buildPageHead() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'19104656的互联',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w500,
),
),
Icon(
Icons.arrow_right_rounded,
size: 24.sp,
)
],
),
Icon(
Icons.add_circle_outline_rounded,
size: 24.sp,
),
],
);
}
_buildSystemNotificationPermissionRow() {
return Visibility(
visible: !controller.isOpenNotificationPermission.value,
child: Container(
decoration: const BoxDecoration(
color: Color(0xFFfdefdf),
),
child: Row(
children: [
Text(
'系统通知未开启,报警消息无法通知'.tr,
style: TextStyle(
color: Color(0xFFea8720),
),
),
Container(
child: Text(
'去开启'.tr,
style: TextStyle(
color: Colors.white,
),
),
)
],
),
),
);
}

View File

@ -1,10 +1,16 @@
import 'package:get/get.dart';
import 'package:starwork_flutter/views/home/home_controller.dart';
import 'package:starwork_flutter/views/login/login_controller.dart';
import 'package:starwork_flutter/views/main/main_controller.dart';
import 'package:starwork_flutter/views/messages/messages_controller.dart';
import 'package:starwork_flutter/views/mine/mine_controller.dart';
class MainBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<MainController>(() => MainController());
Get.lazyPut<HomeController>(() => HomeController());
Get.lazyPut<MessagesController>(() => MessagesController());
Get.lazyPut<MineController>(() => MineController());
}
}

View File

@ -81,6 +81,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.8"
dio:
dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
fake_async:
dependency: transitive
description:
@ -165,6 +181,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.7.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
@ -253,6 +277,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.11.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
path:
dependency: transitive
description:

View File

@ -29,6 +29,8 @@ dependencies:
fluttertoast: ^8.2.8
# 验证码输入框
pinput: ^5.0.1
# 网络请求库
dio: ^5.9.0
dev_dependencies: