Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
44 changes: 38 additions & 6 deletions lib/core/injection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ import 'package:campus_app/utils/dio_utils.dart';
import 'package:campus_app/utils/constants.dart';
import 'package:native_dio_adapter/native_dio_adapter.dart';

// Email-related imports
import 'package:campus_app/pages/email_client/services/email_auth_service.dart';
import 'package:campus_app/pages/email_client/services/imap_email_service.dart';
import 'package:campus_app/pages/email_client/repositories/email_repository.dart';
import 'package:campus_app/pages/email_client/repositories/imap_email_repository.dart';
import 'package:campus_app/pages/email_client/services/email_service.dart';

final sl = GetIt.instance; // service locator

Future<void> init() async {
//!
//! Datasources
//!

//! Datasources
sl.registerSingletonAsync(() async {
final client = Dio();
client.httpClientAdapter = NativeAdapter();
Expand All @@ -63,10 +69,13 @@ Future<void> init() async {
sl.registerLazySingleton(() => NavigationDatasource(appwriteClient: sl()));

//!
//! Repositories
//! Repositories (non-email)
//!

sl.registerLazySingleton(() => BackendRepository(client: sl()));
sl.registerLazySingleton(() {
final Client client = Client().setEndpoint(appwrite).setProject('campus_app');
return BackendRepository(client: client);
});

sl.registerSingletonWithDependencies(
() => NewsRepository(newsDatasource: sl()),
Expand All @@ -87,6 +96,32 @@ Future<void> init() async {
() => TicketRepository(ticketDataSource: sl(), secureStorage: sl()),
);

//!
//! Email dependencies (reordered)
//!

// 1. FlutterSecureStorage is already registered below in “External”

// 2. EmailAuthService (needs secure storage)
sl.registerLazySingleton<EmailAuthService>(
() => EmailAuthService(),
);

// 3. ImapEmailService (low-level IMAP/SMTP)
sl.registerLazySingleton<ImapEmailService>(
() => ImapEmailService(),
);

// 4. EmailRepository (depends on ImapEmailService)
sl.registerLazySingleton<EmailRepository>(
() => ImapEmailRepository(sl<ImapEmailService>()),
);

// 5. EmailService (business logic, depends on EmailRepository)
sl.registerLazySingleton<EmailService>(
() => EmailService(sl<EmailRepository>()),
);

//!
//! Usecases
//!
Expand All @@ -95,17 +130,14 @@ Future<void> init() async {
() => NewsUsecases(newsRepository: sl()),
dependsOn: [NewsRepository],
);

sl.registerSingletonWithDependencies(
() => CalendarUsecases(calendarRepository: sl()),
dependsOn: [CalendarRepository],
);

sl.registerSingletonWithDependencies(
() => MensaUsecases(mensaRepository: sl()),
dependsOn: [MensaRepository],
);

sl.registerLazySingleton(
() => TicketUsecases(ticketRepository: sl()),
);
Expand Down
21 changes: 21 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,25 @@ import 'package:campus_app/pages/calendar/entities/venue_entity.dart';
import 'package:campus_app/utils/pages/main_utils.dart';
import 'package:campus_app/utils/pages/mensa_utils.dart';

import 'package:campus_app/pages/email_client/services/email_service.dart';
import 'package:campus_app/pages/email_client/services/imap_email_service.dart';
import 'package:campus_app/pages/email_client/services/email_auth_service.dart';
import 'package:campus_app/pages/email_client/repositories/email_repository.dart';
import 'package:campus_app/pages/email_client/repositories/imap_email_repository.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:campus_app/pages/email_client/services/email_background_service.dart';




Future<void> main() async {
final WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
// Keeps the native splash screen onscreen until all loading is done
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
BackgroundFetch.registerHeadlessTask(EmailBackgroundService.headlessTask);
// initialize background service
await EmailBackgroundService.init();


// Disable all logs in production mode
if (!kDebugMode) debugPrint = (String? message, {int? wrapWidth}) => '';
Expand Down Expand Up @@ -66,6 +81,9 @@ Future<void> main() async {
// Initializes the provider that handles the app-theme, authentication and other things
ChangeNotifierProvider<SettingsHandler>(create: (_) => SettingsHandler()),
ChangeNotifierProvider<ThemesNotifier>(create: (_) => ThemesNotifier()),
ChangeNotifierProvider<EmailAuthService>(create: (_) => EmailAuthService()),
Provider<EmailRepository>(create: (_) => ImapEmailRepository(ImapEmailService())),
ChangeNotifierProvider<EmailService>(create: (ctx) => EmailService(ctx.read<EmailRepository>()))
],
child: CampusApp(
key: campusAppKey,
Expand All @@ -80,6 +98,9 @@ Future<void> main() async {
// Initializes the provider that handles the app-theme, authentication and other things
ChangeNotifierProvider<SettingsHandler>(create: (_) => SettingsHandler()),
ChangeNotifierProvider<ThemesNotifier>(create: (_) => ThemesNotifier()),
ChangeNotifierProvider<EmailAuthService>(create: (_) => EmailAuthService()),
Provider<EmailRepository>(create: (_) => ImapEmailRepository(ImapEmailService())),
ChangeNotifierProvider<EmailService>(create: (ctx) => EmailService(ctx.read<EmailRepository>()))
],
child: CampusApp(
key: campusAppKey,
Expand Down
37 changes: 37 additions & 0 deletions lib/pages/email_client/email_drawer/archives.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:campus_app/pages/email_client/models/email.dart';
import 'package:campus_app/pages/email_client/services/email_service.dart';
import 'package:campus_app/pages/email_client/widgets/email_tile.dart';
import 'package:campus_app/pages/email_client/email_pages/email_view.dart';

class ArchivesPage extends StatelessWidget {
const ArchivesPage({super.key});

@override
Widget build(BuildContext context) {
final emailService = Provider.of<EmailService>(context, listen: false);
final archivedEmails = emailService.filterEmails('', EmailFolder.archives);

return Scaffold(
appBar: AppBar(title: const Text('Archives')),
body: archivedEmails.isEmpty
? const Center(child: Text('No archived emails'))
: ListView.builder(
itemCount: archivedEmails.length,
itemBuilder: (context, index) {
final email = archivedEmails[index];
return EmailTile(
email: email,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => EmailView(email: email),
),
),
);
},
),
);
}
}
146 changes: 146 additions & 0 deletions lib/pages/email_client/email_drawer/drafts.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:campus_app/pages/email_client/models/email.dart';
import 'package:campus_app/pages/email_client/services/email_service.dart';
import 'package:campus_app/pages/email_client/widgets/email_tile.dart';
import 'package:campus_app/pages/email_client/email_pages/compose_email_screen.dart';

// UI screen to display and manage email drafts
class DraftsPage extends StatefulWidget {
const DraftsPage({super.key});

@override
State<DraftsPage> createState() => _DraftsPageState();
}

class _DraftsPageState extends State<DraftsPage> {
@override
Widget build(BuildContext context) {
final emailService = Provider.of<EmailService>(context); // Access the email service
final selectionController = emailService.selectionController; // For managing multi-selection
final drafts = emailService.allEmails.where((e) => e.folder == EmailFolder.drafts).toList()
..sort((a, b) => b.date.compareTo(a.date)); // Sort drafts by newest first

return Scaffold(
appBar: _buildAppBar(selectionController, drafts, emailService), // Show toolbar with actions
body: drafts.isEmpty
? _buildEmptyState() // Show message if no drafts
: ListView.separated(
itemCount: drafts.length,
separatorBuilder: (_, __) => Divider(
height: 1,
color: Theme.of(context).dividerColor,
),
itemBuilder: (_, index) {
final draft = drafts[index];
return EmailTile(
email: draft,
isSelected: selectionController.isSelected(draft),
onTap: () => _handleEmailTap(draft, selectionController), // Tap to edit
onLongPress: () => _handleEmailLongPress(draft, selectionController), // Long press to select
);
},
),
);
}

// Builds AppBar depending on whether selection mode is active
PreferredSizeWidget _buildAppBar(selectionController, List<Email> drafts, EmailService emailService) {
if (selectionController.isSelecting) {
return AppBar(
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => selectionController.clearSelection(), // Exit selection mode
),
title: Text('${selectionController.selectionCount} selected'),
actions: [
IconButton(
icon: const Icon(Icons.select_all),
onPressed: () => selectionController.selectAll(drafts), // Select all drafts
),
IconButton(
icon: const Icon(Icons.delete_forever),
onPressed: () => _showDeleteConfirmation(selectionController, emailService), // Confirm before deletion
),
],
);
}

// Default AppBar when not selecting
return AppBar(
title: const Text('Drafts'),
);
}

// Widget shown when there are no drafts
Widget _buildEmptyState() {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.drafts_outlined,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'No drafts',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
],
),
);
}

// Handles tapping a draft: open for editing or toggle selection
void _handleEmailTap(Email draft, selectionController) {
if (selectionController.isSelecting) {
selectionController.toggleSelection(draft); // Toggle selected state
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ComposeEmailScreen(draft: draft), // Navigate to compose screen with the draft
),
);
}
}

// Handles long press to enter selection mode
void _handleEmailLongPress(Email draft, selectionController) {
if (!selectionController.isSelecting) {
selectionController.toggleSelection(draft); // Start selecting
}
}

// Show confirmation dialog before permanently deleting selected drafts
void _showDeleteConfirmation(selectionController, EmailService emailService) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Drafts'),
content: Text(
'Are you sure you want to permanently delete ${selectionController.selectionCount} draft(s)?\n\nThis action cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context), // Cancel deletion
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
emailService.deleteEmailsPermanently(selectionController.selectedEmails); // Delete selected drafts
Navigator.pop(context); // Close dialog
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
}
}
Empty file.
37 changes: 37 additions & 0 deletions lib/pages/email_client/email_drawer/sent.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:campus_app/pages/email_client/models/email.dart';
import 'package:campus_app/pages/email_client/services/email_service.dart';
import 'package:campus_app/pages/email_client/widgets/email_tile.dart';
import 'package:campus_app/pages/email_client/email_pages/email_view.dart';

class SentPage extends StatelessWidget {
const SentPage({super.key});

@override
Widget build(BuildContext context) {
final emailService = Provider.of<EmailService>(context, listen: false);
final sentEmails = emailService.filterEmails('', EmailFolder.sent);

return Scaffold(
appBar: AppBar(title: const Text('Sent Emails')),
body: sentEmails.isEmpty
? const Center(child: Text('No sent emails'))
: ListView.builder(
itemCount: sentEmails.length,
itemBuilder: (context, index) {
final email = sentEmails[index];
return EmailTile(
email: email,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => EmailView(email: email),
),
),
);
},
),
);
}
}
Loading