This commit is contained in:
dmit.b
2026-07-05 12:42:04 +03:00
parent f011a121d7
commit 98cd704234
8 changed files with 81 additions and 69 deletions
+1 -15
View File
@@ -11,27 +11,13 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
android:label="family_safety_frontend" android:label="GeoShare"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<service <service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService" android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="location" android:foregroundServiceType="location"
android:exported="false" /> android:exported="false" />
<receiver
android:name="com.pravera.flutter_foreground_task.receiver.AlarmReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.pravera.flutter_foreground_task.action.ALARM" />
</intent-filter>
</receiver>
<receiver
android:name="com.pravera.flutter_foreground_task.receiver.BootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
+2 -2
View File
@@ -1,9 +1,9 @@
class ApiConfig { class ApiConfig {
static String _baseUrl = 'https://family-safety.onrender.com/api'; static String _baseUrl = 'https://test';
static String get baseUrl => _baseUrl; static String get baseUrl => _baseUrl;
static void setBaseUrl(String url) { static void setBaseUrl(String url){
_baseUrl = url.endsWith('/') ? url.substring(0, url.length - 1) : url; _baseUrl = url.endsWith('/') ? url.substring(0, url.length - 1) : url;
} }
+1
View File
@@ -12,6 +12,7 @@ import 'services/settings_service.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
final settings = SettingsService(); final settings = SettingsService();
await settings.initialize();
ApiConfig.setBaseUrl(settings.baseUrl); ApiConfig.setBaseUrl(settings.baseUrl);
final bgGeo = BackgroundGeoService(); final bgGeo = BackgroundGeoService();
await bgGeo.init(); await bgGeo.init();
+8 -15
View File
@@ -30,13 +30,18 @@ class _LoginScreenState extends State<LoginScreen>
_tabController.addListener(() { _tabController.addListener(() {
setState(() {}); setState(() {});
}); });
WidgetsBinding.instance.addPostFrameCallback((_) {
final settings = context.read<SettingsService>(); final settings = context.read<SettingsService>();
_serverUrlController.text = settings.baseUrl; _serverUrlController.text = settings.baseUrl;
_loginController.text = settings.login ?? ""; _loginController.text = settings.login ?? "";
_passwordController.text = settings.pwd ?? ""; _passwordController.text = settings.pwd ?? "";
if (settings.login != null && settings.pwd != null) { if (settings.login != null && settings.pwd != null) {
savedAuth = true; savedAuth = true;
final authProvider = context.read<AuthProvider>();
login(settings.login!, settings.pwd!, authProvider);
Navigator.of(context).pushReplacementNamed('/map');
} }
});
} }
@override @override
@@ -53,15 +58,6 @@ class _LoginScreenState extends State<LoginScreen>
Widget build(BuildContext context) { Widget build(BuildContext context) {
final authProvider = context.watch<AuthProvider>(); final authProvider = context.watch<AuthProvider>();
final settings = context.read<SettingsService>(); final settings = context.read<SettingsService>();
if (savedAuth) {
login(
_loginController.text,
_passwordController.text,
context,
authProvider,
settings,
);
}
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
body: SingleChildScrollView( body: SingleChildScrollView(
@@ -77,7 +73,7 @@ class _LoginScreenState extends State<LoginScreen>
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Text( const Text(
'Family Safety', 'Авторизация',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 32, fontSize: 32,
@@ -174,11 +170,13 @@ class _LoginScreenState extends State<LoginScreen>
} }
} }
if (_tabController.index == 0) { if (_tabController.index == 0) {
print("Login...");
try { try {
await authProvider.login( await authProvider.login(
_loginController.text, _loginController.text,
_passwordController.text, _passwordController.text,
); );
settings.saveAuth(_loginController.text, _passwordController.text);
if (!context.mounted) return; if (!context.mounted) return;
Navigator.of( Navigator.of(
context, context,
@@ -233,15 +231,10 @@ class _LoginScreenState extends State<LoginScreen>
Future<void> login( Future<void> login(
String login, String login,
String pwd, String pwd,
BuildContext ctx,
AuthProvider authProvider, AuthProvider authProvider,
SettingsService settings,
) async { ) async {
try { try {
await authProvider.login(login, pwd); await authProvider.login(login, pwd);
settings.saveAuth(login, pwd);
if (!ctx.mounted) return;
Navigator.of(ctx).pushReplacementNamed('/map');
} catch (e) { } catch (e) {
// Error is handled by provider // Error is handled by provider
} }
+32 -10
View File
@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:GeoShare/services/settings_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
@@ -27,7 +28,7 @@ class _MapScreenState extends State<MapScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initLocationAndShare(); //_initLocationAndShare();
} }
@override @override
@@ -150,7 +151,8 @@ class _MapScreenState extends State<MapScreen> {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) => _WatchMenu( builder: (context) =>
_WatchMenu(
token: authProvider.token, token: authProvider.token,
onAdd: (shareId) async { onAdd: (shareId) async {
await shareProvider.addTracking(shareId, authProvider.token); await shareProvider.addTracking(shareId, authProvider.token);
@@ -207,6 +209,7 @@ class _MapScreenState extends State<MapScreen> {
final authProvider = context.watch<AuthProvider>(); final authProvider = context.watch<AuthProvider>();
final _ = context.watch<MapProvider>(); final _ = context.watch<MapProvider>();
final shareProvider = context.watch<ShareProvider>(); final shareProvider = context.watch<ShareProvider>();
final settings = context.read<SettingsService>();
if (_loading) { if (_loading) {
return Scaffold(body: const Center(child: CircularProgressIndicator())); return Scaffold(body: const Center(child: CircularProgressIndicator()));
@@ -215,7 +218,8 @@ class _MapScreenState extends State<MapScreen> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
'Family Safety Map\n${_center.latitude.toStringAsFixed(4)}, ${_center.longitude.toStringAsFixed(4)}', 'Карта\n${_center.latitude.toStringAsFixed(4)}, ${_center
.longitude.toStringAsFixed(4)}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
actions: [ actions: [
@@ -223,7 +227,10 @@ class _MapScreenState extends State<MapScreen> {
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: _updateGeolocation, onPressed: _updateGeolocation,
), ),
IconButton(icon: const Icon(Icons.share), onPressed: _copyShareLink), IconButton(icon: const Icon(Icons.share), onPressed: () {
_initLocationAndShare();
_copyShareLink();
}),
IconButton( IconButton(
icon: const Icon(Icons.visibility), icon: const Icon(Icons.visibility),
onPressed: _openWatchMenu, onPressed: _openWatchMenu,
@@ -236,6 +243,7 @@ class _MapScreenState extends State<MapScreen> {
await bgGeo.stop(); await bgGeo.stop();
bgGeo.dispose(); bgGeo.dispose();
authProvider.logout(); authProvider.logout();
settings.logOut();
Navigator.of(context).pushReplacementNamed('/'); Navigator.of(context).pushReplacementNamed('/');
}, },
), ),
@@ -287,7 +295,10 @@ class _WatchMenuState extends State<_WatchMenu> {
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom, bottom: MediaQuery
.of(context)
.viewInsets
.bottom,
), ),
child: Container( child: Container(
constraints: const BoxConstraints(maxHeight: 400), constraints: const BoxConstraints(maxHeight: 400),
@@ -297,7 +308,10 @@ class _WatchMenuState extends State<_WatchMenu> {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest, color: Theme
.of(context)
.colorScheme
.surfaceContainerHighest,
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(16), top: Radius.circular(16),
), ),
@@ -354,7 +368,8 @@ class _WatchMenuState extends State<_WatchMenu> {
title: Text(person.name), title: Text(person.name),
subtitle: hasPosition subtitle: hasPosition
? Text( ? Text(
'${person.position!['y'].toStringAsFixed(4)}, ${person.position!['x'].toStringAsFixed(4)}', '${person.position!['y'].toStringAsFixed(4)}, ${person
.position!['x'].toStringAsFixed(4)}',
) )
: const Text('Нет данных'), : const Text('Нет данных'),
trailing: IconButton( trailing: IconButton(
@@ -368,7 +383,10 @@ class _WatchMenuState extends State<_WatchMenu> {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest, color: Theme
.of(context)
.colorScheme
.surfaceContainerHighest,
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(16), bottom: Radius.circular(16),
), ),
@@ -387,7 +405,9 @@ class _WatchMenuState extends State<_WatchMenu> {
), ),
), ),
onSubmitted: (value) { onSubmitted: (value) {
if (value.trim().isNotEmpty) { if (value
.trim()
.isNotEmpty) {
widget.onAdd(value.trim()); widget.onAdd(value.trim());
_controller.clear(); _controller.clear();
} }
@@ -397,7 +417,9 @@ class _WatchMenuState extends State<_WatchMenu> {
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
if (_controller.text.trim().isNotEmpty) { if (_controller.text
.trim()
.isNotEmpty) {
widget.onAdd(_controller.text.trim()); widget.onAdd(_controller.text.trim());
_controller.clear(); _controller.clear();
} }
+3
View File
@@ -8,6 +8,7 @@ class AuthService {
AuthService({http.Client? client}) : _client = client ?? http.Client(); AuthService({http.Client? client}) : _client = client ?? http.Client();
Future<String> login(String login, String password) async { Future<String> login(String login, String password) async {
print(ApiConfig.loginUrl);
final response = await _client.post( final response = await _client.post(
Uri.parse(ApiConfig.loginUrl), Uri.parse(ApiConfig.loginUrl),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@@ -17,6 +18,8 @@ class AuthService {
if (response.statusCode == 200) { if (response.statusCode == 200) {
return response.body; return response.body;
} else { } else {
print(response.statusCode);
print(response.body);
throw Exception('Invalid credentials'); throw Exception('Invalid credentials');
} }
} }
+9 -2
View File
@@ -1,7 +1,6 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class SettingsService { class SettingsService {
SettingsService(){initialize();}
static const String _baseUrlKey = 'server_base_url'; static const String _baseUrlKey = 'server_base_url';
static const String _defaultBaseUrl = static const String _defaultBaseUrl =
'https://family-safety.onrender.com/api'; 'https://family-safety.onrender.com/api';
@@ -13,6 +12,7 @@ class SettingsService {
String get baseUrl => _baseUrl; String get baseUrl => _baseUrl;
String? login; String? login;
String? pwd; String? pwd;
bool loggedIn=false;
Future<void> initialize() async { Future<void> initialize() async {
if (_initialized) return; if (_initialized) return;
@@ -34,7 +34,14 @@ class SettingsService {
pwd = pass; pwd = pass;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString(_loginKey, user); await prefs.setString(_loginKey, user);
await prefs.setString(_loginKey, user); await prefs.setString(_pwdKey, pass);
}
Future<void> logOut() async {
final prefs = await SharedPreferences.getInstance();
login = null;
pwd = null;
await prefs.remove(_loginKey);
await prefs.remove(_pwdKey);
} }
void resetToDefault() { void resetToDefault() {
+2 -2
View File
@@ -1,5 +1,5 @@
name: family_safety_frontend name: GeoShare
description: "A new Flutter project." description: "GeoShare"
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev