This repository has been archived by the owner on Jan 10, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Update README.md and dependencies * Add basic app structure and assets * Implement first models, viewmodels and api calls needed for user auth * Implement TUM-SSO authentication and update README.md and * Clean up auth_handler.dart and update routes * Update userState and userViewModel to use riverpod and rxdart * Refactor code to improve readability * Remove unused import * Clean up code, refactor models, update error-handling
- Loading branch information
1 parent
c853664
commit 280e0c7
Showing
21 changed files
with
1,023 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,31 @@ | ||
# The mobile client for [gocast](https://github.com/TUM-Dev/gocast) | ||
|
||
This mobile client for [gocast](https://github.com/TUM-Dev/gocast) is currently under development by the [iPraktikum Winter 23/24](https://ase.cit.tum.de/teaching/23w/ipraktikum/) on behalf of the TUM Developers. In order not to influence the grading of the students, we would ask you to refrain from code contributions until **March 2023**. Until then, we look forward to your contributions in our other repositories. Thank you for your understanding! | ||
|
||
|
||
## Features | ||
|
||
- [x] Authentication using internal account | ||
- [x] Authentication using TUM SSO | ||
- [ ] Overview of own and publicly available Lectures | ||
- [ ] Ability to watch lectures (single, multi - view and split - view) | ||
- [ ] Bookmark lectures | ||
- [ ] Automatic notifications if lecture starts | ||
- [ ] Ability to search for lectures | ||
- [ ] Ability to download lectures in a data privacy conform manner (non - exportable and remotely deletable) | ||
- [ ] Ability to answer quizzes and feedback requests | ||
|
||
## Config | ||
|
||
1. Make sure to have a local [`gocast`](https://github.com/tum-dev/gocast) instance listening on port `8081`. | ||
|
||
2. Run `$ flutter run` to start the app. | ||
|
||
3. Run `dart fix --apply && dart format ./lib` before commiting new changes. | ||
|
||
## Development | ||
|
||
| Dependency | Usage | Where to download it | | ||
|------------------------------------------|------------------------------------------|----------------------------------------------| | ||
| `Flutter` (includes the `Dart` compiler) | SDK to develop this app | https://docs.flutter.dev/get-started/install | | ||
| A local instance of [`gocast`](https://github.com/tum-dev/gocast) | API to fetch user data & streams | https://github.com/TUM-Dev/gocast#readme | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import 'dart:math'; | ||
import 'package:gocast_mobile/model/course/bookmark_model.dart'; | ||
import 'package:gocast_mobile/model/course/course_model.dart'; | ||
import 'package:gocast_mobile/model/user/user_model.dart'; | ||
import 'package:gocast_mobile/model/user/user_settings_model.dart'; | ||
|
||
class ModelGenerator { | ||
static User generateRandomUser() { | ||
final random = Random(); | ||
final id = random.nextInt(1000); | ||
final name = 'User$id'; | ||
final lastName = 'Last$id'; | ||
final email = '$name.$lastName@example.com'; | ||
final matriculationNumber = 'M$id'; | ||
final lrzId = 'L$id'; | ||
final role = random.nextInt(3) + 1; | ||
final password = 'password$id'; | ||
final courses = List.generate( | ||
random.nextInt(5) + 1, | ||
(index) => Course(id: index, name: 'Course$index'), | ||
); | ||
final administeredCourses = List.generate( | ||
random.nextInt(3) + 1, | ||
(index) => Course(id: index, name: 'Administered Course$index'), | ||
); | ||
final pinnedCourses = List.generate( | ||
random.nextInt(3) + 1, | ||
(index) => Course(id: index, name: 'Pinned Course$index'), | ||
); | ||
final settings = List.generate( | ||
random.nextInt(3) + 1, | ||
(index) => UserSetting( | ||
id: index, | ||
userId: id, | ||
type: UserSettingType.values[random.nextInt(3)], | ||
value: 'Value$index', | ||
), | ||
); | ||
final bookmarks = List.generate( | ||
random.nextInt(3) + 1, | ||
(index) => Bookmark( | ||
id: index, | ||
userId: id, | ||
title: 'Bookmark$index', | ||
url: 'https://example.com/bookmark$index', | ||
), | ||
); | ||
|
||
return User( | ||
id: id, | ||
name: name, | ||
lastName: lastName, | ||
email: email, | ||
matriculationNumber: matriculationNumber, | ||
lrzId: lrzId, | ||
role: role, | ||
password: password, | ||
courses: courses, | ||
administeredCourses: administeredCourses, | ||
pinnedCourses: pinnedCourses, | ||
settings: settings, | ||
bookmarks: bookmarks, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import 'package:cookie_jar/cookie_jar.dart'; | ||
import 'package:dio/dio.dart'; | ||
import 'package:dio_cookie_manager/dio_cookie_manager.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as webview; | ||
import 'package:gocast_mobile/base/helpers/model_generator.dart'; | ||
import 'package:gocast_mobile/model/token_model.dart'; | ||
import 'package:gocast_mobile/model/user/user_model.dart'; | ||
import 'package:gocast_mobile/routes.dart'; | ||
|
||
class AuthHandler { | ||
static Future<void> basicAuth( | ||
String username, | ||
String password, | ||
) async { | ||
const url = Routes.basicLogin; | ||
final cookieJar = CookieJar(); | ||
final dio = Dio( | ||
BaseOptions( | ||
followRedirects: false, | ||
validateStatus: (status) { | ||
return status! < 500; | ||
}, | ||
), | ||
); | ||
dio.interceptors.add(CookieManager(cookieJar)); | ||
|
||
final formData = FormData.fromMap({ | ||
'username': username, | ||
'password': password, | ||
}); | ||
|
||
try { | ||
await dio.post( | ||
url, | ||
data: formData, | ||
); | ||
} catch (e) { | ||
// Throw the error so it can be caught and handled in the signIn method | ||
throw Exception('Request error: $e'); | ||
} | ||
|
||
List<Cookie> cookies = await cookieJar.loadForRequest(Uri.parse(url)); | ||
|
||
// Save jwt token | ||
await Token.saveToken(cookies); | ||
} | ||
|
||
static Future<void> ssoAuth(BuildContext context) async { | ||
debugPrint('ssoAuth started'); | ||
// Redirect the user to the Shibboleth login page | ||
debugPrint('Login URL: $Routes.ssoLogin'); | ||
|
||
// Open the login page in a web view | ||
await Navigator.push( | ||
context, | ||
MaterialPageRoute( | ||
builder: (context) => webview.InAppWebView( | ||
initialUrlRequest: | ||
webview.URLRequest(url: Uri.parse(Routes.ssoLogin)), | ||
onLoadStop: | ||
(webview.InAppWebViewController controller, Uri? url) async { | ||
debugPrint('Page load stopped: $url'); | ||
|
||
try { | ||
final cookieManager = webview.CookieManager.instance(); | ||
List<webview.Cookie> cookies = | ||
await cookieManager.getCookies(url: url!); | ||
|
||
// Save jwt token | ||
await Token.saveToken( | ||
cookies.map((c) => Cookie(c.name, c.value)).toList(), | ||
); | ||
|
||
// Redirect back to app | ||
if (url.toString().startsWith(Routes.ssoRedirect)) { | ||
debugPrint('Redirect URL detected: $url'); | ||
|
||
// Close the web view and go back to the app | ||
debugPrint('Closing web view and returning to app'); | ||
Navigator.pop(context); | ||
} | ||
} catch (e) { | ||
// Throw the error so it can be caught and handled by the caller of ssoAuth | ||
throw Exception('Error in ssoAuth: $e'); | ||
} | ||
}, | ||
), | ||
), | ||
); | ||
} | ||
|
||
// Generate user mock for testing the views until API/v2 is implemented | ||
static Future<User> fetchUser() async { | ||
// TODO: Add GET:/user endpoint in gocast API to fetch current user information | ||
return ModelGenerator.generateRandomUser(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import 'dart:async'; | ||
import 'dart:developer'; | ||
|
||
import 'package:bloc/bloc.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
|
||
class AppBlocObserver extends BlocObserver { | ||
const AppBlocObserver(); | ||
|
||
@override | ||
void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) { | ||
super.onChange(bloc, change); | ||
log('onChange(${bloc.runtimeType}, $change)'); | ||
} | ||
|
||
@override | ||
void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) { | ||
log('onError(${bloc.runtimeType}, $error, $stackTrace)'); | ||
super.onError(bloc, error, stackTrace); | ||
} | ||
} | ||
|
||
Future<void> bootstrap(FutureOr<Widget> Function() builder) async { | ||
FlutterError.onError = (details) { | ||
log(details.exceptionAsString(), stackTrace: details.stack); | ||
}; | ||
|
||
Bloc.observer = const AppBlocObserver(); | ||
|
||
// Add cross-flavor configuration here | ||
|
||
runApp(await builder()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,64 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:gocast_mobile/model/user/user_state_model.dart'; | ||
import 'package:gocast_mobile/viewModels/user_viewmodel.dart'; | ||
import 'package:gocast_mobile/views/home_view.dart'; | ||
import 'package:gocast_mobile/views/login_view.dart'; | ||
import 'package:gocast_mobile/views/welcome_view.dart'; | ||
|
||
final userViewModel = Provider((ref) => UserViewModel()); | ||
|
||
final userStateProvider = StreamProvider<UserState>((ref) { | ||
return ref.watch(userViewModel).current.stream; | ||
}); | ||
|
||
void main() { | ||
runApp(const MyApp()); | ||
runApp( | ||
const ProviderScope( | ||
child: App(), | ||
), | ||
); | ||
} | ||
|
||
class MyApp extends StatelessWidget { | ||
const MyApp({super.key}); | ||
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); | ||
|
||
class App extends ConsumerWidget { | ||
const App({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final userState = ref.watch(userStateProvider); | ||
|
||
if (userState.error != null) { | ||
WidgetsBinding.instance.addPostFrameCallback((_) { | ||
scaffoldMessengerKey.currentState!.showSnackBar( | ||
SnackBar(content: Text('Error: ${userState.error}')), | ||
); | ||
}); | ||
} | ||
|
||
return MaterialApp( | ||
scaffoldMessengerKey: scaffoldMessengerKey, | ||
title: 'gocast', | ||
debugShowCheckedModeBanner: false, | ||
theme: ThemeData( | ||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xff0065bd)), | ||
useMaterial3: true, | ||
), | ||
home: const Scaffold( | ||
body: Center( | ||
child: Text("Hello, iPraktikum! 👋"), | ||
appBarTheme: AppBarTheme( | ||
backgroundColor: Theme.of(context).colorScheme.inversePrimary, | ||
), | ||
), | ||
home: userState.value?.user == null | ||
? const LoginView(key: Key('loginView')) | ||
: const HomeView(key: Key('homeView')), | ||
routes: { | ||
'/welcome': (context) => userState.value?.user == null | ||
? const LoginView(key: Key('loginView')) | ||
: const WelcomeView(key: Key('welcomeView')), | ||
'/home': (context) => userState.value?.user == null | ||
? const LoginView(key: Key('loginView')) | ||
: const HomeView(key: Key('homeView')), | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
class Bookmark { | ||
Bookmark({ | ||
required this.id, | ||
required this.userId, | ||
required this.title, | ||
required this.url, | ||
}); | ||
|
||
int id; | ||
int userId; | ||
String title; | ||
String url; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class Course { | ||
Course({ | ||
required this.id, | ||
required this.name, | ||
}); | ||
|
||
int id; | ||
String name; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import 'dart:io'; | ||
import 'package:shared_preferences/shared_preferences.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
|
||
// This class will be extended once the API/v2 is implemented | ||
class Token { | ||
static Future<void> saveToken(List<Cookie> cookies) async { | ||
for (var cookie in cookies) { | ||
if (cookie.name == 'jwt') { | ||
SharedPreferences prefs = await SharedPreferences.getInstance(); | ||
await prefs.setString('jwt', cookie.value); | ||
|
||
// DEBUG: Check cookie | ||
String? jwt = prefs.getString('jwt'); | ||
debugPrint('Current jwt: $jwt'); | ||
return; | ||
} | ||
} | ||
|
||
// Handle error when no jwt cookie is found | ||
throw Exception('No JWT cookie found'); | ||
} | ||
} |
Oops, something went wrong.