Google authentikáció bevezetése
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../services/project_service.dart';
|
||||
|
||||
/// „Csatlakozás közös projekthez" — a Supabase-en elérhető közös
|
||||
/// projektek listája, egy koppintásos csatlakozással.
|
||||
///
|
||||
/// A lista a ts_shared_projects view-ból jön (név, tulajdonos, tagszám,
|
||||
/// és hogy a felhasználó már tag-e). Hálózat nélkül a lista nem érhető el —
|
||||
/// erről barátságos üzenet szól; a MÁR csatlakozott projektek viszont
|
||||
/// offline is teljes értékűen használhatók, ez csak az ÚJ csatlakozáshoz
|
||||
/// kell.
|
||||
class JoinProjectSheet extends StatefulWidget {
|
||||
const JoinProjectSheet({super.key});
|
||||
|
||||
static Future<void> show() {
|
||||
return Get.bottomSheet(
|
||||
const JoinProjectSheet(),
|
||||
isScrollControlled: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<JoinProjectSheet> createState() => _JoinProjectSheetState();
|
||||
}
|
||||
|
||||
class _JoinProjectSheetState extends State<JoinProjectSheet> {
|
||||
late Future<List<Map<String, dynamic>>> _future;
|
||||
String? _joiningId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// TODO
|
||||
// _future = ProjectService.to.fetchSharedProjects();
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() {
|
||||
// TODO
|
||||
//_future = ProjectService.to.fetchSharedProjects();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _join(Map<String, dynamic> row) async {
|
||||
setState(() => _joiningId = row['id'] as String);
|
||||
try {
|
||||
// TODO
|
||||
//final project = await ProjectService.to.joinSharedProject(row);
|
||||
Get.back();
|
||||
// Get.snackbar(
|
||||
// 'Csatlakozva',
|
||||
// '„${project.name}" — az adatok letöltése a háttérben fut.',
|
||||
// snackPosition: SnackPosition.BOTTOM,
|
||||
// );
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Hiba a csatlakozáskor',
|
||||
e.toString(),
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: const Color(0xFFB71C1C),
|
||||
colorText: const Color(0xFFFFFFFF),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() => _joiningId = null);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Material(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.75,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 42,
|
||||
height: 4,
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.outlineVariant,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.groups_outlined),
|
||||
const SizedBox(width: 8),
|
||||
Text('Közös projektek',
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Frissítés',
|
||||
onPressed: _reload,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 16),
|
||||
Flexible(
|
||||
child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _future,
|
||||
builder: (context, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(40),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
if (snap.hasError) {
|
||||
return _Message(
|
||||
icon: Icons.cloud_off,
|
||||
text: 'A közös projektlista most nem érhető el.\n'
|
||||
'Ellenőrizd a hálózatot, majd frissíts.\n\n'
|
||||
'A már csatlakozott projektjeid offline is\n'
|
||||
'teljes értékűen használhatók.',
|
||||
onRetry: _reload,
|
||||
);
|
||||
}
|
||||
final rows = snap.data ?? [];
|
||||
if (rows.isEmpty) {
|
||||
return const _Message(
|
||||
icon: Icons.folder_off_outlined,
|
||||
text: 'Még nincs közös projekt.\n'
|
||||
'Hozz létre egyet „Közös projekt" típussal,\n'
|
||||
'és a csapat többi tagja itt fogja látni.',
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: rows.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (context, i) {
|
||||
final r = rows[i];
|
||||
final isMember = r['is_member'] == true;
|
||||
final joining = _joiningId == r['id'];
|
||||
|
||||
return ListTile(
|
||||
leading: Icon(Icons.folder_shared_outlined,
|
||||
color: _hexColor(r['color'] as String?)),
|
||||
title: Text(r['name'] as String? ?? ''),
|
||||
subtitle: Text(
|
||||
[
|
||||
if ((r['owner_name'] as String?)?.isNotEmpty ??
|
||||
false)
|
||||
r['owner_name'],
|
||||
'${r['member_count'] ?? 0} tag',
|
||||
if ((r['client'] as String?)?.isNotEmpty ?? false)
|
||||
r['client'],
|
||||
].join(' · '),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
trailing: isMember
|
||||
? const Chip(
|
||||
label: Text('Tag vagy'),
|
||||
visualDensity: VisualDensity.compact,
|
||||
)
|
||||
: joining
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2),
|
||||
)
|
||||
: FilledButton.tonal(
|
||||
onPressed: () => _join(r),
|
||||
child: const Text('Csatlakozás'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Message extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final VoidCallback? onRetry;
|
||||
const _Message({required this.icon, required this.text, this.onRetry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 40, color: Colors.grey.shade400),
|
||||
const SizedBox(height: 12),
|
||||
Text(text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey.shade600)),
|
||||
if (onRetry != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
TextButton(onPressed: onRetry, child: const Text('Újra')),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Color _hexColor(String? hex) {
|
||||
if (hex == null || hex.isEmpty) return const Color(0xFF185FA5);
|
||||
final h = hex.replaceFirst('#', '');
|
||||
return Color(int.parse(h.length == 6 ? 'FF$h' : h, radix: 16));
|
||||
}
|
||||
Reference in New Issue
Block a user