Add AddFeaturesDialog widget and enhance error handling in login form and account settings dialog
This commit is contained in:
@@ -21,6 +21,7 @@ import 'login_form.dart' show LoginForm;
|
||||
import 'file_explorer.dart';
|
||||
import '../widgets/organization_settings_dialog.dart';
|
||||
import '../widgets/account_settings_dialog.dart';
|
||||
import '../widgets/add_features_dialog.dart';
|
||||
import '../widgets/audio_player_bar.dart';
|
||||
import '../injection.dart';
|
||||
|
||||
@@ -231,6 +232,13 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddFeaturesDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => const AddFeaturesDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrgRow(BuildContext context) {
|
||||
return BlocBuilder<OrganizationBloc, OrganizationState>(
|
||||
builder: (context, state) {
|
||||
@@ -384,6 +392,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
IconData icon, {
|
||||
bool isAvatar = false,
|
||||
VoidCallback? onTap,
|
||||
bool showSoonBadge = false,
|
||||
}) {
|
||||
final isSelected = _selectedTab == label;
|
||||
final highlightColor = const Color.fromARGB(255, 100, 200, 255);
|
||||
@@ -391,7 +400,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
? AppTheme.primaryText
|
||||
: AppTheme.secondaryText;
|
||||
|
||||
return GestureDetector(
|
||||
Widget button = GestureDetector(
|
||||
onTap:
|
||||
onTap ??
|
||||
() {
|
||||
@@ -508,6 +517,36 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (showSoonBadge) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
button,
|
||||
Positioned(
|
||||
top: -6,
|
||||
right: -12,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.accentColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Text(
|
||||
'soon',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -599,12 +638,17 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
children: [
|
||||
_buildNavButton('Drive', Icons.cloud),
|
||||
const SizedBox(width: 16),
|
||||
_buildNavButton('Mail', Icons.mail),
|
||||
_buildNavButton(
|
||||
'Mail',
|
||||
Icons.mail,
|
||||
showSoonBadge: true,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
_buildNavButton(
|
||||
'Add',
|
||||
Icons.add,
|
||||
onTap: () {},
|
||||
onTap: () =>
|
||||
_showAddFeaturesDialog(context),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if (hasSelectedOrg)
|
||||
|
||||
@@ -354,20 +354,18 @@ class _LoginFormState extends State<LoginForm> {
|
||||
isLoading: state is AuthLoading,
|
||||
onPressed: () {
|
||||
if (_usernameController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Username is required'),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_usernameHasError = true;
|
||||
_usernameErrorText = 'username is required';
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (_isSignup) {
|
||||
if (_passwordController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Password is required'),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_passwordHasError = true;
|
||||
_passwordErrorText = 'password is required';
|
||||
});
|
||||
return;
|
||||
}
|
||||
context.read<AuthBloc>().add(
|
||||
@@ -387,11 +385,11 @@ class _LoginFormState extends State<LoginForm> {
|
||||
);
|
||||
} else {
|
||||
if (_passwordController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Password is required'),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_passwordHasError = true;
|
||||
_passwordErrorText =
|
||||
'password is required';
|
||||
});
|
||||
return;
|
||||
}
|
||||
context.read<AuthBloc>().add(
|
||||
|
||||
@@ -35,6 +35,14 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
late TextEditingController _newPasswordController;
|
||||
late TextEditingController _confirmPasswordController;
|
||||
|
||||
// Password field error state
|
||||
bool _currentPasswordHasError = false;
|
||||
bool _newPasswordHasError = false;
|
||||
bool _confirmPasswordHasError = false;
|
||||
String? _currentPasswordErrorText;
|
||||
String? _newPasswordErrorText;
|
||||
String? _confirmPasswordErrorText;
|
||||
|
||||
User? _currentUser;
|
||||
|
||||
@override
|
||||
@@ -236,12 +244,50 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
}
|
||||
|
||||
Future<void> _changePassword() async {
|
||||
// Clear previous errors
|
||||
setState(() {
|
||||
_currentPasswordHasError = false;
|
||||
_newPasswordHasError = false;
|
||||
_confirmPasswordHasError = false;
|
||||
_currentPasswordErrorText = null;
|
||||
_newPasswordErrorText = null;
|
||||
_confirmPasswordErrorText = null;
|
||||
});
|
||||
|
||||
// Validate current password
|
||||
if (_currentPasswordController.text.isEmpty) {
|
||||
setState(() {
|
||||
_currentPasswordHasError = true;
|
||||
_currentPasswordErrorText = 'current password is required';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate new password
|
||||
if (_newPasswordController.text.isEmpty) {
|
||||
setState(() {
|
||||
_newPasswordHasError = true;
|
||||
_newPasswordErrorText = 'new password is required';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate confirm password
|
||||
if (_confirmPasswordController.text.isEmpty) {
|
||||
setState(() {
|
||||
_confirmPasswordHasError = true;
|
||||
_confirmPasswordErrorText = 'please confirm your password';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate passwords match
|
||||
if (_newPasswordController.text != _confirmPasswordController.text) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Passwords do not match')));
|
||||
}
|
||||
setState(() {
|
||||
_newPasswordHasError = true;
|
||||
_confirmPasswordHasError = true;
|
||||
_confirmPasswordErrorText = 'passwords do not match';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -258,15 +304,34 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
// Clear fields
|
||||
// Clear fields and errors
|
||||
_currentPasswordController.clear();
|
||||
_newPasswordController.clear();
|
||||
_confirmPasswordController.clear();
|
||||
setState(() {
|
||||
_currentPasswordHasError = false;
|
||||
_newPasswordHasError = false;
|
||||
_confirmPasswordHasError = false;
|
||||
_currentPasswordErrorText = null;
|
||||
_newPasswordErrorText = null;
|
||||
_confirmPasswordErrorText = null;
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to change password: $e')),
|
||||
);
|
||||
final errorMsg = e.toString().toLowerCase();
|
||||
if (errorMsg.contains('incorrect') ||
|
||||
errorMsg.contains('invalid') ||
|
||||
errorMsg.contains('wrong')) {
|
||||
setState(() {
|
||||
_currentPasswordHasError = true;
|
||||
_currentPasswordErrorText = 'incorrect password';
|
||||
_currentPasswordController.clear();
|
||||
});
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to change password: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
@@ -784,7 +849,9 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
color: AppTheme.primaryBackground.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
color: _currentPasswordHasError
|
||||
? Colors.red
|
||||
: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
@@ -792,6 +859,15 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
obscureText: true,
|
||||
cursorColor: AppTheme.accentColor,
|
||||
style: TextStyle(color: AppTheme.primaryText),
|
||||
onChanged: (_) {
|
||||
if (_currentPasswordHasError ||
|
||||
_currentPasswordErrorText != null) {
|
||||
setState(() {
|
||||
_currentPasswordHasError = false;
|
||||
_currentPasswordErrorText = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter current password',
|
||||
hintStyle: TextStyle(color: AppTheme.secondaryText),
|
||||
@@ -800,6 +876,14 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_currentPasswordErrorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6, left: 8),
|
||||
child: Text(
|
||||
_currentPasswordErrorText!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// New Password
|
||||
@@ -816,7 +900,9 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
color: AppTheme.primaryBackground.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
color: _newPasswordHasError
|
||||
? Colors.red
|
||||
: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
@@ -824,6 +910,14 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
obscureText: true,
|
||||
cursorColor: AppTheme.accentColor,
|
||||
style: TextStyle(color: AppTheme.primaryText),
|
||||
onChanged: (_) {
|
||||
if (_newPasswordHasError || _newPasswordErrorText != null) {
|
||||
setState(() {
|
||||
_newPasswordHasError = false;
|
||||
_newPasswordErrorText = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter new password',
|
||||
hintStyle: TextStyle(color: AppTheme.secondaryText),
|
||||
@@ -832,6 +926,14 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_newPasswordErrorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6, left: 8),
|
||||
child: Text(
|
||||
_newPasswordErrorText!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Confirm Password
|
||||
@@ -848,7 +950,9 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
color: AppTheme.primaryBackground.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
color: _confirmPasswordHasError
|
||||
? Colors.red
|
||||
: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
@@ -856,6 +960,15 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
obscureText: true,
|
||||
cursorColor: AppTheme.accentColor,
|
||||
style: TextStyle(color: AppTheme.primaryText),
|
||||
onChanged: (_) {
|
||||
if (_confirmPasswordHasError ||
|
||||
_confirmPasswordErrorText != null) {
|
||||
setState(() {
|
||||
_confirmPasswordHasError = false;
|
||||
_confirmPasswordErrorText = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Confirm new password',
|
||||
hintStyle: TextStyle(color: AppTheme.secondaryText),
|
||||
@@ -864,6 +977,14 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_confirmPasswordErrorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6, left: 8),
|
||||
child: Text(
|
||||
_confirmPasswordErrorText!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Change Password Button
|
||||
|
||||
284
b0esche_cloud/lib/widgets/add_features_dialog.dart
Normal file
284
b0esche_cloud/lib/widgets/add_features_dialog.dart
Normal file
@@ -0,0 +1,284 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
|
||||
class AddFeaturesDialog extends StatelessWidget {
|
||||
const AddFeaturesDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: AppTheme.primaryBackground,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Container(
|
||||
width: 500,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add_circle_outline,
|
||||
color: AppTheme.accentColor,
|
||||
size: 28,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Add Features',
|
||||
style: TextStyle(
|
||||
color: AppTheme.primaryText,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: Icon(Icons.close, color: AppTheme.secondaryText),
|
||||
splashRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Enhance your workspace with additional features',
|
||||
style: TextStyle(color: AppTheme.secondaryText, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Features list
|
||||
_FeatureCard(
|
||||
icon: Icons.calendar_month,
|
||||
title: 'Calendar',
|
||||
description:
|
||||
'Schedule events, set reminders, and manage your time efficiently. Sync with your team and never miss important deadlines.',
|
||||
onAdd: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Calendar feature coming soon!'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_FeatureCard(
|
||||
icon: Icons.dashboard_customize,
|
||||
title: 'Board',
|
||||
description:
|
||||
'Organize tasks with kanban-style boards. Create columns, drag cards, and track progress visually across your projects.',
|
||||
onAdd: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Board feature coming soon!')),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Footer hint
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.accentColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: AppTheme.accentColor,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'More features will be available soon. Stay tuned!',
|
||||
style: TextStyle(
|
||||
color: AppTheme.accentColor,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FeatureCard extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final VoidCallback onAdd;
|
||||
|
||||
const _FeatureCard({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.onAdd,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_FeatureCard> createState() => _FeatureCardState();
|
||||
}
|
||||
|
||||
class _FeatureCardState extends State<_FeatureCard> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? Colors.white.withValues(alpha: 0.08)
|
||||
: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: _isHovered
|
||||
? AppTheme.accentColor.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Feature icon with soon badge
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppTheme.accentColor.withValues(alpha: 0.3),
|
||||
AppTheme.accentColor.withValues(alpha: 0.1),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
color: AppTheme.accentColor,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: -6,
|
||||
right: -8,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.accentColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Text(
|
||||
'soon',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Feature details
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: TextStyle(
|
||||
color: AppTheme.primaryText,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.description,
|
||||
style: TextStyle(
|
||||
color: AppTheme.secondaryText,
|
||||
fontSize: 13,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Add button
|
||||
_AddButton(onPressed: widget.onAdd),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AddButton extends StatefulWidget {
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _AddButton({required this.onPressed});
|
||||
|
||||
@override
|
||||
State<_AddButton> createState() => _AddButtonState();
|
||||
}
|
||||
|
||||
class _AddButtonState extends State<_AddButton> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
child: GestureDetector(
|
||||
onTap: widget.onPressed,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? AppTheme.accentColor
|
||||
: AppTheme.accentColor.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: AppTheme.accentColor.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: _isHovered ? Colors.black : AppTheme.accentColor,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -69,10 +69,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -205,10 +205,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
version: "2.2.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -535,10 +535,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -988,10 +988,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.6"
|
||||
version: "6.4.1"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1092,10 +1092,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_avfoundation
|
||||
sha256: f46e9e20f1fe429760cf4dc118761336320d1bec0f50d255930c2355f2defb5b
|
||||
sha256: f93b93a3baa12ca0ff7d00ca8bc60c1ecd96865568a01ff0c18a99853ee201a5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.1"
|
||||
version: "2.9.3"
|
||||
video_player_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user