Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4b18498
feat: Introduce user event planner in Calendar tab
AzadWanli Jun 3, 2025
ec84f80
feat: Implement multi-view personal planner with calendar_view
AzadWanli Jun 13, 2025
7b292f2
feat(planner): Implement consistent theming across all calendar views
AzadWanli Jun 17, 2025
d7e8657
feat(planner): Implement edit and delete functionality for events
AzadWanli Jun 18, 2025
781b225
feat(planner): Add support for multi-day events
AzadWanli Jun 18, 2025
03840bc
feat(planner): Implement recurring events
AzadWanli Jun 20, 2025
c3a6eae
event color feauture
AzadWanli Jun 23, 2025
e5f1608
refactor(planner): modularize PlannerPage
AzadWanli Jul 4, 2025
3d68638
planner: fix EventController lifecycle & sync strategy
AzadWanli Jul 4, 2025
52bf6d1
feat(planner): implement Hive for local data persistence
AzadWanli Jul 10, 2025
b4e3b3b
feat(planner): integrate Calendar events with Planner & refine event …
AzadWanli Jul 11, 2025
b9cb200
feat(planner): refactor feature to Clean Architecture
AzadWanli Jul 13, 2025
3345cb0
fix(planner): timezone bugs & multi‑day events
AzadWanli Jul 16, 2025
5dc630f
feat(calendar)
AzadWanli Jul 19, 2025
fa04422
feat(ui):
AzadWanli Jul 20, 2025
7e68c2e
feat(planner): smoother new‑event flow & clearer date format
AzadWanli Jul 20, 2025
159cc7b
feat(planner): smoother new‑event flow & clearer date format
AzadWanli Jul 20, 2025
a559ff3
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 21, 2025
1b68e00
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 21, 2025
f1c2f17
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 21, 2025
99ceff5
Revert "Merge branch 'feature/planner' of https://github.com/astarub/…
AzadWanli Jul 21, 2025
ecb28c6
feat(planner): persistent colours
AzadWanli Jul 21, 2025
4cdbd7b
planner:
AzadWanli Jul 21, 2025
b8e18a2
planner:
AzadWanli Jul 21, 2025
03b743a
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 23, 2025
06998ad
feat(planner): Adding some comments
AzadWanli Jul 23, 2025
79053fe
feat(planner): Adding some comments
AzadWanli Jul 23, 2025
37bf639
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 23, 2025
bae0d56
planner/mensa: add/remove of meals as planner events
AzadWanli Jul 26, 2025
639a5b2
planner/mensa: add/remove of meals as planner events
AzadWanli Jul 26, 2025
f717b60
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 26, 2025
df8564f
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 26, 2025
d4cc026
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
AzadWanli Jul 26, 2025
e598d91
add studytimer
Ramasalha Jul 28, 2025
f308e62
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
Ramasalha Jul 28, 2025
818fd26
Merge remote-tracking branch 'origin/master' into feature/planner
domai-tb Jul 28, 2025
cc99086
Merge branch 'master' into feature/planner
domai-tb Aug 3, 2025
5a0b416
akte Arbeit Stauts
Ramasalha Oct 6, 2025
e670442
Merge branch 'feature/planner' of https://github.com/astarub/campus_a…
Ramasalha Oct 6, 2025
81cdd87
Curses and Timer
Ramasalha Dec 10, 2025
1975fde
Pulled planner + update feed UI
ZakiaKhadri Mar 24, 2026
e39aa91
Merge pull request #217 from astarub/planner-feed-update
domai-tb Mar 29, 2026
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
2 changes: 1 addition & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ if (keystorePropertiesFile.exists()) {
}

android {
compileSdk = 35
compileSdk = 36

namespace = "de.asta_bochum.campus_app"

Expand Down
12 changes: 10 additions & 2 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.jvmargs=-Xmx4096M -Dkotlin.daemon.jvm.options=-Xmx2048M
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true

# Improve build performance
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true

# Kotlin code style
kotlin.code.style=official
9 changes: 9 additions & 0 deletions lib/appwrite/appwrite_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:appwrite/appwrite.dart';

class AppwriteClient {
static Client getClient() {
return Client()
..setEndpoint('https://api-dev-app.asta-bochum.de/v1')
..setProject('campus_app');
}
}
53 changes: 53 additions & 0 deletions lib/appwrite/course_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart';
import 'appwrite_client.dart';

class CourseService {
final Databases db = Databases(AppwriteClient.getClient());
final String databaseId = "courses";

// alle Fakultäts-Collection-IDs
final List<String> facultyCollections = [
"68ff4371002b37d0785c",
"69024b840015bdc729d8",
"6907ab8900018027e6e0",
"6907df0b000832908ded",
"690908e2000b559395bb",
"690938f100160232c270",
"6909392b001c4f530c4a",
"69093939003a6bb8c995",
"690939530003f03086f2",
"690939aa0031228ba3e2",
"69093b05001a55bb235f",
"69093b23001315a99b29",
"69093b5e000863a43a2c",
"69093b8b003b8a9295d4",
"69093bb200383849e0ad",
"69093bcb0003eff326d7",
"69093be4000d2adc74c9",
"691b83300017df2f3c8e",
"69093bfd002faa70dae0",
"69093c15001f6a1b1db5",
"69093c340021e62e4273",
];

Future<List<Map<String, dynamic>>> getAllCourses() async {
List<Map<String, dynamic>> all = [];

for (final colId in facultyCollections) {
final docs = await db.listDocuments(
databaseId: databaseId,
collectionId: colId,
queries: [
Query.limit(5000),
],
);

for (final d in docs.documents) {
all.add(d.data);
}
}

return all;
}
}
3 changes: 3 additions & 0 deletions lib/core/failures.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ class NoDataFailure extends Failure {}

/// caching goes wrong
class CachFailure extends Failure {}

/// local storage error
class StorageFailure extends Failure {}
23 changes: 22 additions & 1 deletion lib/core/injection.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'package:appwrite/appwrite.dart';
import 'package:campus_app/pages/planner/entities/planner_event_entity.dart';
import 'package:campus_app/pages/planner/planner_datasource.dart';
import 'package:campus_app/pages/planner/planner_repository.dart';
import 'package:campus_app/pages/planner/planner_usecases.dart';
import 'package:campus_app/utils/pages/wallet_utils.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
Expand Down Expand Up @@ -63,6 +67,10 @@ Future<void> init() async {
);
});

sl.registerSingletonAsync<PlannerDatasource>(() async {
await Hive.openBox<PlannerEventEntity>('planner_events');
return PlannerDatasource();
});
//!
//! Repositories
//!
Expand Down Expand Up @@ -91,6 +99,10 @@ Future<void> init() async {
() => TicketRepository(ticketDataSource: sl(), secureStorage: sl()),
);

sl.registerSingletonWithDependencies<PlannerRepository>(
() => PlannerRepository(sl()),
dependsOn: [PlannerDatasource],
);
//!
//! Usecases
//!
Expand All @@ -114,6 +126,11 @@ Future<void> init() async {
() => TicketUsecases(ticketRepository: sl()),
);

sl.registerSingletonWithDependencies<PlannerUsecases>(
() => PlannerUsecases(sl()),
dependsOn: [PlannerRepository],
);

//!
//! Utils
//!
Expand All @@ -136,7 +153,11 @@ Future<void> init() async {
//!

//sl.registerLazySingleton(http.Client.new);
sl.registerLazySingleton(Dio.new);
sl.registerLazySingleton((){
final client = Dio();
client.httpClientAdapter = NativeAdapter();
return client;
});
sl.registerLazySingleton(CookieJar.new);
sl.registerLazySingleton(
() => const FlutterSecureStorage(
Expand Down
32 changes: 18 additions & 14 deletions lib/l10n/l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ import 'l10n_en.dart';
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());

final String localeName;

static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}

static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();

/// A list of this localizations delegate along with the default localizations
/// delegates.
Expand All @@ -82,7 +84,8 @@ abstract class AppLocalizations {
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
Expand Down Expand Up @@ -198,7 +201,8 @@ abstract class AppLocalizations {
String get enter_totp;
}

class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();

@override
Expand All @@ -207,25 +211,25 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>
}

@override
bool isSupported(Locale locale) => <String>['de', 'en'].contains(locale.languageCode);
bool isSupported(Locale locale) =>
<String>['de', 'en'].contains(locale.languageCode);

@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}

AppLocalizations lookupAppLocalizations(Locale locale) {


// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de': return AppLocalizationsDe();
case 'en': return AppLocalizationsEn();
case 'de':
return AppLocalizationsDe();
case 'en':
return AppLocalizationsEn();
}

throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.'
);
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.');
}
12 changes: 8 additions & 4 deletions lib/l10n/l10n_de.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get helloWorld => 'Hallo Welt!';

@override
String get serverFailureMessage => 'Serverdaten konnten nicht geladen werden.';
String get serverFailureMessage =>
'Serverdaten konnten nicht geladen werden.';

@override
String get generalFailureMessage => 'Ein Fehler ist aufgetreten.';
Expand All @@ -24,16 +25,19 @@ class AppLocalizationsDe extends AppLocalizations {
String get unexpectedError => 'Ein unerwarteter Fehler ist aufgetreten...';

@override
String get invalid2FATokenFailureMessage => 'Dein Einmalcode (TOTP) is ungültig. Bitte versuche es erneut!';
String get invalid2FATokenFailureMessage =>
'Dein Einmalcode (TOTP) is ungültig. Bitte versuche es erneut!';

@override
String get invalidLoginIDAndPasswordFailureMessage => 'Die Anmeldedaten sind ungültig!';
String get invalidLoginIDAndPasswordFailureMessage =>
'Die Anmeldedaten sind ungültig!';

@override
String get welcome => 'Willkommen!';

@override
String get login_prompt => 'Bitte melde dich mit deiner RUB-ID und deinem Passwort an.';
String get login_prompt =>
'Bitte melde dich mit deiner RUB-ID und deinem Passwort an.';

@override
String get rubid => 'Login-ID von deinem RUB-Account';
Expand Down
6 changes: 4 additions & 2 deletions lib/l10n/l10n_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get unexpectedError => 'An unexpected error occured...';

@override
String get invalid2FATokenFailureMessage => 'Your TOTP is incorrect. Please try again!';
String get invalid2FATokenFailureMessage =>
'Your TOTP is incorrect. Please try again!';

@override
String get invalidLoginIDAndPasswordFailureMessage => 'The credentials are invalid!';
String get invalidLoginIDAndPasswordFailureMessage =>
'The credentials are invalid!';

@override
String get welcome => 'Welcome!';
Expand Down
11 changes: 10 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:io';
import 'dart:convert';

import 'package:campus_app/core/injection.dart';
import 'package:campus_app/pages/mensa/planner_helpers/mensa_day_notifier.dart';
import 'package:campus_app/utils/constants.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -29,6 +31,9 @@ import 'package:campus_app/pages/calendar/entities/organizer_entity.dart';
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/planner/planner_state.dart';
import 'package:campus_app/pages/planner/entities/planner_event_entity.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Future<void> main() async {
final WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
Expand All @@ -46,10 +51,10 @@ Future<void> main() async {
Hive.registerAdapter(CategoryAdapter());
Hive.registerAdapter(NewsEntityAdapter());
Hive.registerAdapter(DishEntityAdapter());
Hive.registerAdapter(PlannerEventEntityAdapter());

// Initialize injection container
await ic.init();

// Checks if the app is in release mode and initializes sentry
// REMOVE THIS CHECK IF YOU WISH TO RUN THE APP IN RELEASE MODE OTHERWISE THE APP WILL NOT RUN
if (kReleaseMode) {
Expand All @@ -66,6 +71,8 @@ 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<PlannerState>(create: (_) => PlannerState(sl())..init()),
ChangeNotifierProvider(create: (_) => MensaDayNotifier()),
],
child: CampusApp(
key: campusAppKey,
Expand All @@ -80,6 +87,8 @@ 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<PlannerState>(create: (_) => PlannerState(sl())..init()),
ChangeNotifierProvider(create: (_) => MensaDayNotifier()),
],
child: CampusApp(
key: campusAppKey,
Expand Down
17 changes: 16 additions & 1 deletion lib/pages/calendar/calendar_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import 'package:campus_app/utils/widgets/campus_button.dart';
import 'package:campus_app/utils/widgets/campus_icon_button.dart';
import 'package:campus_app/utils/widgets/styled_html.dart';
import 'package:share_plus/share_plus.dart';
import 'package:campus_app/pages/planner/planner_state.dart';
import 'package:campus_app/pages/calendar/calendar_event_planner_mapper.dart';

class CalendarDetailPage extends StatefulWidget {
final Event event;
Expand All @@ -30,16 +32,27 @@ class CalendarDetailPage extends StatefulWidget {
class _CalendarDetailState extends State<CalendarDetailPage> {
final CalendarRepository calendarRepository = sl<CalendarRepository>();
final BackendRepository backendRepository = sl<BackendRepository>();
late PlannerState _plannerState;

bool savedEvent = false;
bool get _isAlreadyInPlanner => _plannerState.events.any((e) => e.id == widget.event.id.toString());

/// Function that updates the saved event state and shows an info
/// message inside a [SnackBar]
Future<void> saveEventAndShowMessage() async {
setState(() {
savedEvent = !savedEvent;
});

if (savedEvent) {
if (!_isAlreadyInPlanner) {
await _plannerState.addEvent(widget.event.toPlannerEvent());
}
} else {
if (_isAlreadyInPlanner) {
await _plannerState.deleteEvent(widget.event.id.toString());
}
}
if (!mounted) return;
try {
final SettingsHandler settingsHandler = Provider.of<SettingsHandler>(context, listen: false);

Expand Down Expand Up @@ -71,6 +84,8 @@ class _CalendarDetailState extends State<CalendarDetailPage> {
@override
void initState() {
super.initState();
_plannerState = context.read<PlannerState>();
if (_isAlreadyInPlanner) savedEvent = true;

calendarRepository.updateSavedEvents().then((savedEvents) {
savedEvents.fold((failure) => null, (list) {
Expand Down
16 changes: 16 additions & 0 deletions lib/pages/calendar/calendar_event_planner_mapper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:campus_app/pages/calendar/entities/event_entity.dart';
import 'package:campus_app/pages/planner/entities/planner_event_entity.dart';

extension EventX on Event {
PlannerEventEntity toPlannerEvent({Color fallbackColor = const Color.fromARGB(255, 160, 19, 9)}) {
return PlannerEventEntity(
id: id.toString(),
title: title,
description: description,
startDateTime: startDate,
endDateTime: endDate,
color: fallbackColor,
);
}
}
Loading