diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 00000000..dbaf35b5 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +untranslated-messages-file: desiredFileName.txt diff --git a/lib/base/helpers/mock_data.dart b/lib/base/helpers/mock_data.dart deleted file mode 100644 index 46ad3c04..00000000 --- a/lib/base/helpers/mock_data.dart +++ /dev/null @@ -1,187 +0,0 @@ -import 'dart:math'; - -import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; - -/// This class contains mock data for testing and development purposes. -/// It is used to simulate the API responses. -/// It is not used in production. -/// It will be used for Testing, Demo and Development purposes only. -/// -/// This class will be moved to testing folder in the Future. -class MockData { - static final _random = Random(); - static const String mockEmail = 'ge123abc'; - static const String mockPassword = 'secure'; - static final List mockUserSettings = mockUser.settings; - static final List mockCourses = _defaultCourses(); - static final List mockUserCourses = _mockUserCoursesDefault(3); - static final List mockUserPinned = _mockPinCoursesDefault(2); - static final List mockPublicCourses = mockCourses; - static final List liveCourses = mockCourses - .where((course) => course.streams.any((stream) => stream.liveNow)) - .toList(); - static final List mockDownloadedCourses = _mockDownloadedCourses(2); - static final List messages = [ - 'Hello, welcome to the lecture!', - 'Can you explain this topic further?', - 'Sure, let me elaborate on that.', - 'I have a question about the homework, do we calculate this using the formula from the lecture?', - ]; - - static List _defaultCourses() { - // Generate courses without considering liveNow status - List courses = List.generate(10, (_) => _generateRandomCourse()); - - // Randomly select 1 or 2 courses to be live - int liveCoursesCount = _random.nextBool() ? 1 : 2; - List liveIndices = List.generate(courses.length, (index) => index) - ..shuffle(); - liveIndices.take(liveCoursesCount).forEach((index) { - courses[index] = _makeCourseLive(courses[index]); - }); - - return courses; - } - - static List _mockUserCoursesDefault(int count) { - if (count >= mockCourses.length) { - return List.from(mockCourses); - } - var shuffledCourses = List.from(mockCourses)..shuffle(); - return shuffledCourses.take(count).toList(); - } - - static List _mockPinCoursesDefault(int count) { - if (count >= mockUserCourses.length) { - return List.from(mockCourses); - } - var shuffledCourses = List.from(mockUserCourses)..shuffle(); - return shuffledCourses.take(count).toList(); - } - - /* static List _mockPublicCoursesDefault(int count) { - if (count >= mockCourses.length) { - return List.from(mockCourses); - } - var shuffledCourses = List.from(mockCourses)..shuffle(); - return shuffledCourses.take(count).toList(); - }*/ - - static List _mockDownloadedCourses(int count) { - if (count >= mockUserCourses.length) { - return List.from(mockCourses); - } - var shuffledCourses = List.from(mockUserCourses)..shuffle(); - return shuffledCourses.take(count).toList(); - } - - static final User mockUser = User( - id: 1, - email: mockEmail, - name: 'Max', - lastName: 'Mustermann', - role: 4, - settings: [ - UserSetting( - type: UserSettingType.PREFERRED_NAME, - value: 'The Boss', - ), - UserSetting( - type: UserSettingType.GREETING, - value: 'Moin', - ), - ], - ); - - static Course _generateRandomCourse() { - // Generating a unique course name - String topic = _topics[_random.nextInt(_topics.length)]; - String adjective = _adjectives[_random.nextInt(_adjectives.length)]; - String format = _formats[_random.nextInt(_formats.length)]; - String courseName = '$adjective $topic $format'; - String prefix = _random.nextBool() ? 'IN0' : 'CIT0'; - int number = - _random.nextInt(900) + 100; // Generates a number from 100 to 999 - String courseSlug = '$prefix$number'; - // Constructing a slug - // Other attributes - String playlistUrl = - 'assets/sample.mp4'; // Replace with actual playlist URL'https://zdf-hls-15.akamaized.net/hls/live/2016498/de/high/master.m3u8' - bool vodEnabled = _random.nextBool(); - String cameraPresetPreferences = _random.nextBool() ? 'HD' : 'SD'; - bool liveNow = false; - - return Course( - name: courseName, - slug: courseSlug, - // imagePath: AppImages.course1, // Uncomment and set accordingly - vODEnabled: vodEnabled, - cameraPresetPreferences: cameraPresetPreferences, - semester: Semester( - year: _random.nextInt(5) + 2020, // Random year between 2018 and 2022 - teachingTerm: _random.nextBool() ? 'Winter' : 'Summer', - ), - streams: [ - Stream( - name: 'Lecture', - playlistUrl: playlistUrl, - liveNow: liveNow, - ), - Stream( - name: 'Tutorial', - playlistUrl: playlistUrl, - liveNow: liveNow, - ), - ], - ); - } - - static Course _makeCourseLive(Course course) { - // Clone the course with one of its streams set to liveNow - return Course( - // Copy all existing fields - name: course.name, - slug: course.slug, - // imagePath: course.imagePath, // Uncomment and set accordingly - vODEnabled: course.vODEnabled, - cameraPresetPreferences: course.cameraPresetPreferences, - semester: course.semester, - streams: course.streams - .map( - (stream) => Stream( - name: stream.name, - playlistUrl: stream.playlistUrl, - liveNow: true, - ), - ) - .toList(), - ); - } - - static const List _topics = [ - 'Quantum Mechanics', - 'Software Engineering', - 'UI/UX Design', - 'Artificial Intelligence', - 'Machine Learning', - 'Data Science', - 'Computer Vision', - 'Robotics', - 'Computer Graphics', - ]; - - static const List _adjectives = [ - 'Advanced', - 'Introductory', - 'Applied', - 'Conceptual', - ]; - - static const List _formats = [ - 'Workshop', - 'Seminar', - 'Lecture Series', - 'Lab', - 'Tutorial', - ]; -} diff --git a/lib/base/networking/api/handler/grpc_handler.dart b/lib/base/networking/api/handler/grpc_handler.dart index 88d082f0..89ec8f9f 100644 --- a/lib/base/networking/api/handler/grpc_handler.dart +++ b/lib/base/networking/api/handler/grpc_handler.dart @@ -22,7 +22,7 @@ class GrpcHandler { _channel = ClientChannel( host, port: port, - options: const ChannelOptions(credentials: ChannelCredentials.insecure()), + options: const ChannelOptions(credentials: ChannelCredentials.secure()), ); } diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 8bf881bb..c984bc54 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - /// AppConfig - Defines the configuration for the application. class AppConfig { static const String appName = 'GoCast Mobile'; @@ -8,16 +6,7 @@ class AppConfig { AppConfig._(); // Private constructor - // Determine the root URL based on the platform - // Used for development only. Once the api is deployed, this can be ignored. - static String get _rootUrl { - if (Platform.isAndroid) { - return 'http://10.0.2.2:8081/api'; - } else if (Platform.isIOS) { - return 'http://localhost:8081/api'; - } - throw UnsupportedError('Unsupported platform'); - } + static String get _rootUrl => 'https://1279.test.live.mm.rbg.tum.de'; // Authentication URLs static String get basicAuthUrl => @@ -28,17 +17,14 @@ class AppConfig { static String get ssoRedirectUrl => 'https://live.rbg.tum.de'; // gRPC routes - static String get grpcHost { - return Platform.isAndroid ? '10.0.2.2' : 'localhost'; - } + static String get grpcHost => 'grpc-1279.test.live.mm.rbg.tum.de'; - static const int grpcPort = 8081; + static const int grpcPort = 443; } /// Routes - Defines HTTP and gRPC routes for the application. class Routes { Routes._(); // Private constructor - // HTTP routes static String get basicLogin => AppConfig.basicAuthUrl; diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb new file mode 100644 index 00000000..08f51e1d --- /dev/null +++ b/lib/l10n/app_de.arb @@ -0,0 +1,99 @@ +{ + "language": "Deutsch", + "@language": { + "description": "Die aktuelle Sprache." + }, + "my_courses": "Meine Kurse", + "public_courses": "Öffentliche Kurse", + "live_now": "Jetzt Live", + "pinned_courses": "Angeheftete Kurse", + "pinned_empty": "Sie haben keine angehefteten Kurse.", + "pin": "Anheften", + "unpin": "Lösen", + "pinned": "Angeheftet", + "downloads": "Heruntergeladen", + "download": "Herunterladen", + "notification": "Benachrichtigung", + "notifications": "Benachrichtigungen", + "no_downloaded_courses": "Sie haben keine Kurse heruntergeladen.", + "no_notifications_found": "Keine Benachrichtigungen gefunden.", + "banner_notification": "Banner-Benachrichtigung", + "recent_uploads": "Neueste Uploads", + "feature_notifications": "Feature Notifications", + "confirm_unpin_title": "Lösen Bestätigen", + "confirm_unpin_message": "Sind Sie sicher, dass Sie diesen Kurs lösen möchten?", + "newest_first": "Neueste Zuerst", + "oldest_first": "Älteste Zuerst", + "chat_is_hidden": "Chat ist versteckt", + "chat_is_disabled_for_this_lecture": "Chat ist für diese Vorlesung deaktiviert", + "combined_view": "Kombinierte Ansicht", + "camera_view": "Kameraansicht", + "presentation_view": "Präsentationsansicht", + "split_view": "Geteilte Ansicht", + "starting_download": "Download beginnt...", + "delete": "Löschen", + "confirm_delete": "Löschung Bestätigen", + "confirm_delete_message": "Sind Sie sicher, dass Sie diesen Download löschen möchten?", + "logout_title": "Abmelden", + "logout_message": "Möchten Sie alle Ihre Downloads löschen und sich abmelden?", + "yes": "Ja", + "no": "Nein", + "tum_login": "TUM-Login", + "continue_without": "Fortfahren ohne", + "use_an_internal_account": "Ein internes Konto verwenden", + "welcome_to_gocast": "Willkommen bei GoCast", + "your_lectures_on_the_go": "Ihre Vorlesungen unterwegs", + "type_a_message": "Eine Nachricht eingeben...", + "search": "Suchen", + "cancel": "Abbrechen", + "account_settings": "Kontoeinstellungen", + "settings": "Einstellungen", + "preferred_greeting": "Bevorzugte Begrüßung", + "push_notifications": "Push-Benachrichtigungen", + "choose_theme": "Thema Wählen", + "dark": "Dunkel", + "light": "Hell", + "system_default": "Systemstandard", + "download_over_wifi_only": "Nur über WLAN herunterladen", + "video_playback_speed": "Videowiedergabegeschwindigkeit", + "playback_speed": "Wiedergabegeschwindigkeit", + "add_custom_playback_speed": "Benutzerdefinierte Wiedergabegeschwindigkeit hinzufügen", + "custom_playback_speed": "Benutzerdefinierte Wiedergabegeschwindigkeit", + "logout": "Abmelden", + "more": "Mehr", + "about_us": "Über Uns", + "privacy_policy": "Datenschutzbestimmungen", + "terms_and_conditions": "Allgemeine Geschäftsbedingungen", + "no_courses_found": "Keine Kurse gefunden.", + "download_confirmation_title": "Download-Bestätigung", + "download_confirmation_message": "Sie nutzen mobile Daten. Möchten Sie das Video über mobile Daten herunterladen?", + "download_not_allowed": "Download nicht erlaubt", + "download_not_allowed_message": "Sie befinden sich derzeit in einem Mobilfunknetz. Das Video kann aufgrund Ihrer Einstellungen nicht über mobile Daten heruntergeladen werden.", + "auth_required_title": "Authentifizierung erforderlich", + "auth_required_message": "Bitte melden Sie sich an, um auf diese Funktion zuzugreifen.", + "add_custom_speed": "Benutzerdefinierte Geschwindigkeit hinzufügen", + "enter_speed": "Geschwindigkeit eingeben (z.B. 1.7)", + "add": "Hinzufügen", + "edit_profile": "Profil bearbeiten", + "preferred_name_saved": "Bevorzugter Name gespeichert: {name}", + "name_change_limitation": "Der bevorzugte Name kann nur alle 3 Monate geändert werden", + "error_occurred": "Ein Fehler ist aufgetreten", + "change_limitation_detail": "Sie können dies nur alle drei Monate ändern.", + "enter_preferred_name": "Geben Sie Ihren bevorzugten Namen ein", + "enter_preferred_name_prompt": "Bitte geben Sie einen bevorzugten Namen ein", + "preferred_name": "Bevorzugter Name", + "save": "Speichern", + "number_too_long": "Nummer ist zu lang", + "enter_number_between": "Bitte geben Sie eine Zahl zwischen\n0.25 und 2.0 ein", + "turn_on_notifications": "Benachrichtigungen einschalten?", + "notifications_description": "Erhalten Sie sofortige Updates zu Live-Vorlesungen, bevorstehenden Quizzen und wichtigen Ankündigungen. Verpassen Sie keinen Moment Ihrer akademischen Laufbahn.", + "enable_notifications": "Benachrichtigungen aktivieren", + "skip": "Überspringen", + "forgot_password": "Passwort vergessen?", + "username": "Benutzername", + "password": "Passwort", + "enter_your_password": "Geben Sie Ihr Passwort ein", + "home": "Startseite", + "language_selection": "Sprache", + "language_selection_description": "Wählen Sie Ihre bevorzugte Sprache aus" +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 00000000..c5da5659 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,99 @@ +{ + "language": "English", + "@language": { + "description": "The current language." + }, + "my_courses": "My Courses", + "public_courses": "Public Courses", + "live_now": "Live Now", + "pinned_courses": "Pinned Courses", + "pinned_empty": "You have no pinned courses.", + "pin": "Pin", + "unpin": "Unpin", + "pinned": "Pinned", + "downloads": "Downloads", + "download": "Download", + "notification": "Notification", + "notifications": "Notifications", + "no_downloaded_courses": "You have no downloaded courses.", + "no_notifications_found": "No notifications found.", + "banner_notification": "Banner Alerts", + "recent_uploads": "Recent Uploads", + "feature_notifications": "Feature Notifications", + "confirm_unpin_title": "Confirm Unpin", + "confirm_unpin_message": "Are you sure you want to unpin this course?", + "newest_first": "Newest First", + "oldest_first": "Oldest First", + "chat_is_hidden": "Chat is hidden", + "chat_is_disabled_for_this_lecture": "Chat is disabled for this lecture", + "combined_view": "Combined View", + "camera_view": "Camera View", + "presentation_view": "Presentation View", + "split_view": "Split View", + "starting_download": "Starting download...", + "delete": "Delete", + "confirm_delete": "Confirm Deletion", + "confirm_delete_message": "Are you sure you want to delete this download?", + "logout_title": "Logout", + "logout_message": "Would you like to delete all your downloads and logout?", + "yes": "Yes", + "no": "No", + "tum_login": "TUM Login", + "continue_without": "Continue without", + "use_an_internal_account": "Use an internal account", + "welcome_to_gocast": "Welcome to GoCast", + "your_lectures_on_the_go": "Your Lectures on the Go", + "type_a_message": "Type a message...", + "search": "Search", + "cancel": "Cancel", + "account_settings": "Account Settings", + "settings": "Settings", + "preferred_greeting": "Preferred Greeting", + "push_notifications": "Push Notifications", + "choose_theme": "Choose Theme", + "dark": "Dark", + "light": "Light", + "system_default": "System Default", + "download_over_wifi_only": "Download over WiFi only", + "video_playback_speed": "Video Playback Speed", + "playback_speed": "Playback Speed", + "add_custom_playback_speed": "Add Custom Playback Speed", + "custom_playback_speed": "Custom Playback Speed", + "logout": "Logout", + "more": "More", + "about_us": "About Us", + "privacy_policy": "Privacy Policy", + "terms_and_conditions": "Terms and Conditions", + "no_courses_found": "No courses found.", + "download_confirmation_title": "Download Confirmation", + "download_confirmation_message": "You are on mobile data. Would you like to download the video over mobile data?", + "download_not_allowed": "Download not allowed", + "download_not_allowed_message": "You are currently on mobile data. Video cannot be downloaded over mobile data due to your settings.", + "auth_required_title": "Authentication Required", + "auth_required_message": "You need to be authenticated to access this content.", + "add_custom_speed": "Add Custom Speed", + "enter_speed": "Enter speed (e.g., 1.7)", + "add": "Add", + "edit_profile": "Edit Profile", + "preferred_name_saved": "Preferred name saved: {name}", + "name_change_limitation": "Preferred name can only be changed every 3 months", + "error_occurred": "An error occurred", + "change_limitation_detail": "You can change this once every three months.", + "enter_preferred_name": "Enter your preferred name", + "enter_preferred_name_prompt": "Please enter a preferred name", + "preferred_name": "Preferred Name", + "save": "Save", + "number_too_long": "Number is too long", + "enter_number_between": "Please enter a number between\n0.25 and 2.0", + "turn_on_notifications": "Turn on Notifications?", + "notifications_description": "Receive instant updates on live lectures, upcoming quizzes, and important announcements. Never miss a beat in your academic journey.", + "enable_notifications": "Enable notifications", + "skip": "Skip", + "forgot_password": "Forgot Password?", + "username": "username", + "password": "password", + "enter_your_password": "Enter your password", + "home": "Home", + "language_selection": "Language", + "language_selection_description": "Select your preferred language" +} \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb new file mode 100644 index 00000000..f789e0e1 --- /dev/null +++ b/lib/l10n/app_es.arb @@ -0,0 +1,99 @@ +{ + "language": "Español", + "@language": { + "description": "El idioma actual." + }, + "my_courses": "Mis Cursos", + "public_courses": "Cursos Públicos", + "live_now": "En Vivo Ahora", + "pinned_courses": "Cursos Anclados", + "pinned_empty": "No tienes cursos anclados.", + "pin": "Anclar", + "unpin": "Desanclar", + "pinned": "Anclado", + "downloads": "Descargas", + "download": "Descargar", + "notification": "Notificación", + "notifications": "Notificaciones", + "no_downloaded_courses": "No tienes cursos descargados.", + "no_notifications_found": "No se encontraron notificaciones.", + "banner_notification": "Notificación de Banner", + "recent_uploads": "Cargas Recientes", + "feature_notifications": "Feature Notifications", + "confirm_unpin_title": "Confirmar Desanclaje", + "confirm_unpin_message": "¿Estás seguro de que quieres desanclar este curso?", + "newest_first": "Los Más Nuevos Primero", + "oldest_first": "Los Más Antiguos Primero", + "chat_is_hidden": "El chat está oculto", + "chat_is_disabled_for_this_lecture": "El chat está deshabilitado para esta conferencia", + "combined_view": "Vista Combinada", + "camera_view": "Vista de Cámara", + "presentation_view": "Vista de Presentación", + "split_view": "Vista Dividida", + "starting_download": "Iniciando descarga...", + "delete": "Eliminar", + "confirm_delete": "Confirmar Eliminación", + "confirm_delete_message": "¿Estás seguro de que quieres eliminar esta descarga?", + "logout_title": "Cerrar Sesión", + "logout_message": "¿Quieres eliminar todas tus descargas y cerrar sesión?", + "yes": "Sí", + "no": "No", + "tum_login": "Inicio de Sesión TUM", + "continue_without": "Continuar sin", + "use_an_internal_account": "Usar una cuenta interna", + "welcome_to_gocast": "Bienvenido a GoCast", + "your_lectures_on_the_go": "Tus Conferencias en Movimiento", + "type_a_message": "Escribe un mensaje...", + "search": "Buscar", + "cancel": "Cancelar", + "account_settings": "Configuración de la Cuenta", + "settings": "Configuraciones", + "preferred_greeting": "Saludo Preferido", + "push_notifications": "Notificaciones Push", + "choose_theme": "Elegir Tema", + "dark": "Oscuro", + "light": "Claro", + "system_default": "Predeterminado del Sistema", + "download_over_wifi_only": "Descargar solo por WiFi", + "video_playback_speed": "Velocidad de Reproducción de Video", + "playback_speed": "Velocidad de Reproducción", + "add_custom_playback_speed": "Agregar Velocidad de Reproducción Personalizada", + "custom_playback_speed": "Velocidad de Reproducción Personalizada", + "logout": "Cerrar Sesión", + "more": "Más", + "about_us": "Acerca de Nosotros", + "privacy_policy": "Política de Privacidad", + "terms_and_conditions": "Términos y Condiciones", + "no_courses_found": "No se encontraron cursos.", + "download_confirmation_title": "Confirmación de Descarga", + "download_confirmation_message": "Estás en datos móviles. ¿Quieres descargar el video usando datos móviles?", + "download_not_allowed": "Descarga no permitida", + "download_not_allowed_message": "Actualmente estás en datos móviles. El video no se puede descargar usando datos móviles debido a tus configuraciones.", + "auth_required_title": "Autenticación Requerida", + "auth_required_message": "Por favor, inicia sesión para acceder a esta función.", + "add_custom_speed": "Agregar Velocidad Personalizada", + "enter_speed": "Ingresa la velocidad (por ejemplo, 1.7)", + "add": "Agregar", + "edit_profile": "Editar Perfil", + "preferred_name_saved": "Nombre preferido guardado: {name}", + "name_change_limitation": "El nombre preferido solo se puede cambiar cada 3 meses", + "error_occurred": "Ocurrió un error", + "change_limitation_detail": "Puedes cambiar esto una vez cada tres meses.", + "enter_preferred_name": "Ingresa tu nombre preferido", + "enter_preferred_name_prompt": "Por favor, ingresa un nombre preferido", + "preferred_name": "Nombre Preferido", + "save": "Guardar", + "number_too_long": "El número es demasiado largo", + "enter_number_between": "Por favor, introduce un número entre\n0.25 y 2.0", + "turn_on_notifications": "¿Activar notificaciones?", + "notifications_description": "Recibe actualizaciones instantáneas sobre conferencias en vivo, próximos cuestionarios y anuncios importantes. Nunca te pierdas un detalle en tu trayectoria académica.", + "enable_notifications": "Activar notificaciones", + "skip": "Omitir", + "forgot_password": "¿Olvidaste la contraseña?", + "username": "nombre de usuario", + "password": "contraseña", + "enter_your_password": "Introduce tu contraseña", + "home": "Inicio", + "language_selection": "Idioma", + "language_selection_description": "Selecciona tu idioma preferido." +} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb new file mode 100644 index 00000000..04907259 --- /dev/null +++ b/lib/l10n/app_fr.arb @@ -0,0 +1,99 @@ +{ + "language": "Français", + "@language": { + "description": "La langue actuelle." + }, + "my_courses": "Mes Cours", + "public_courses": "Cours Publics", + "live_now": "En Direct", + "pinned_courses": "Cours Épinglés", + "pinned_empty": "Vous n'avez aucun cours épinglé.", + "pin": "Épingler", + "unpin": "Détacher", + "pinned": "Épinglé", + "downloads": "Téléchargements", + "download": "Télécharger", + "notification": "Notification", + "notifications": "Notifications", + "no_downloaded_courses": "Vous n'avez aucun cours téléchargé.", + "no_notifications_found": "Aucune notification trouvée.", + "banner_notification": "Alertes Bannière", + "recent_uploads": "Derniers Téléchargements", + "feature_notifications": "Notifications de Fonctionnalités", + "confirm_unpin_title": "Confirmer le Détachement", + "confirm_unpin_message": "Êtes-vous sûr de vouloir détacher ce cours?", + "newest_first": "Le Plus Récent en Premier", + "oldest_first": "Le Plus Ancien en Premier", + "chat_is_hidden": "Le chat est caché", + "chat_is_disabled_for_this_lecture": "Le chat est désactivé pour cette conférence", + "combined_view": "Vue Combinée", + "camera_view": "Vue Caméra", + "presentation_view": "Vue de Présentation", + "split_view": "Vue Partagée", + "starting_download": "Début du téléchargement...", + "delete": "Supprimer", + "confirm_delete": "Confirmer la Suppression", + "confirm_delete_message": "Êtes-vous sûr de vouloir supprimer ce téléchargement?", + "logout_title": "Se Déconnecter", + "logout_message": "Souhaitez-vous supprimer tous vos téléchargements et vous déconnecter?", + "yes": "Oui", + "no": "Non", + "tum_login": "Connexion TUM", + "continue_without": "Continuer sans", + "use_an_internal_account": "Utiliser un compte interne", + "welcome_to_gocast": "Bienvenue sur GoCast", + "your_lectures_on_the_go": "Vos conférences en déplacement", + "type_a_message": "Tapez un message...", + "search": "Rechercher", + "cancel": "Annuler", + "account_settings": "Paramètres du Compte", + "settings": "Paramètres", + "preferred_greeting": "Salutation Préférée", + "push_notifications": "Notifications Push", + "choose_theme": "Choisir le Thème", + "dark": "Sombre", + "light": "Clair", + "system_default": "Défaut du Système", + "download_over_wifi_only": "Télécharger uniquement via WiFi", + "video_playback_speed": "Vitesse de Lecture Vidéo", + "playback_speed": "Vitesse de Lecture", + "add_custom_playback_speed": "Ajouter une Vitesse de Lecture Personnalisée", + "custom_playback_speed": "Vitesse de Lecture Personnalisée", + "logout": "Se Déconnecter", + "more": "Plus", + "about_us": "À Propos de Nous", + "privacy_policy": "Politique de Confidentialité", + "terms_and_conditions": "Conditions Générales", + "no_courses_found": "Aucun cours trouvé.", + "download_confirmation_title": "Confirmation de Téléchargement", + "download_confirmation_message": "Vous utilisez des données mobiles. Voulez-vous télécharger la vidéo en utilisant les données mobiles ?", + "download_not_allowed": "Téléchargement non autorisé", + "download_not_allowed_message": "Vous utilisez actuellement des données mobiles. La vidéo ne peut pas être téléchargée en utilisant les données mobiles en raison de vos paramètres.", + "auth_required_title": "Authentification requise", + "auth_required_message": "Veuillez vous connecter pour accéder à cette fonctionnalité.", + "add_custom_speed": "Ajouter une Vitesse Personnalisée", + "enter_speed": "Entrez la vitesse (par exemple, 1.7)", + "add": "Ajouter", + "edit_profile": "Modifier le Profil", + "preferred_name_saved": "Nom préféré enregistré : {name}", + "name_change_limitation": "Le nom préféré ne peut être changé que tous les 3 mois", + "error_occurred": "Une erreur s'est produite", + "change_limitation_detail": "Vous pouvez changer cela une fois tous les trois mois.", + "enter_preferred_name": "Entrez votre nom préféré", + "enter_preferred_name_prompt": "Veuillez entrer un nom préféré", + "preferred_name": "Nom Préféré", + "save": "Enregistrer", + "number_too_long": "Le numéro est trop long", + "enter_number_between": "Veuillez entrer un nombre entre\n0.25 et 2.0", + "turn_on_notifications": "Activer les notifications ?", + "notifications_description": "Recevez des mises à jour instantanées sur les conférences en direct, les quiz à venir et les annonces importantes. Ne manquez jamais un moment clé de votre parcours académique.", + "enable_notifications": "Activer les notifications", + "skip": "Passer", + "forgot_password": "Mot de passe oublié ?", + "username": "nom d'utilisateur", + "password": "mot de passe", + "enter_your_password": "Entrez votre mot de passe", + "home": "Accueil", + "language_selection": "Langue", + "language_selection_description": "Choisissez votre langue préférée." +} diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart new file mode 100644 index 00000000..8cd076cc --- /dev/null +++ b/lib/l10n/l10n.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class L10n { + static final all = [ + const Locale('en'), + const Locale('de'), + const Locale('fr'), + const Locale('es'), + ]; +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 97160994..45041552 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/models/user/user_state_model.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:gocast_mobile/utils/UserPreferences.dart'; import 'package:gocast_mobile/utils/globals.dart'; import 'package:gocast_mobile/utils/theme.dart'; import 'package:gocast_mobile/navigation_tab.dart'; @@ -11,6 +12,9 @@ import 'package:gocast_mobile/views/on_boarding_view/welcome_screen_view.dart'; import 'package:logger/logger.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:gocast_mobile/l10n/l10n.dart'; final scaffoldMessengerKey = GlobalKey(); @@ -18,6 +22,7 @@ Future main() async { Logger.level = Level.info; WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + await UserPreferences.init(); runApp( const ProviderScope( @@ -46,6 +51,14 @@ class App extends ConsumerWidget { _setupNotifications(ref, userState); return MaterialApp( + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: L10n.all, + locale: Locale(UserPreferences.getLanguage()), theme: appTheme, // Your light theme darkTheme: darkAppTheme, // Define your dark theme themeMode: diff --git a/lib/utils/UserPreferences.dart b/lib/utils/UserPreferences.dart new file mode 100644 index 00000000..d92f5506 --- /dev/null +++ b/lib/utils/UserPreferences.dart @@ -0,0 +1,27 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class UserPreferences { + static SharedPreferences? _preferences; + + static const _keyLanguage = 'language'; + + static Future init() async => _preferences = await SharedPreferences.getInstance(); + + static Future setLanguage(String language) async => await _preferences?.setString(_keyLanguage, language); + + static String getLanguage() => _preferences?.getString(_keyLanguage) ?? 'en'; + + static String getLanguageName(String lang) { + switch (lang) { + case 'en': + return 'English'; + case 'es': + return 'Español'; + case 'fr': + return 'Français'; + case 'de': + return 'Deutsch'; + } + return 'English'; + } +} diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index cf8ce7cd..4ff37e79 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/base/networking/api/handler/chat_handler.dart'; import 'package:gocast_mobile/base/networking/api/handler/grpc_handler.dart'; @@ -16,7 +17,7 @@ class ChatViewModel extends StateNotifier { try { final messages = await ChatHandlers(_grpcHandler).getChatMessages(streamId); - state = state.copyWith(messages: messages, isLoading: false); + state = state.copyWith(messages: messages, isLoading: false, accessDenied: false); } catch (e) { state = state.copyWith( error: e as AppError, @@ -26,6 +27,27 @@ class ChatViewModel extends StateNotifier { } } + Future updateMessages(int streamId) async { + state = state.copyWith(isLoading: true); + state = state.clearError(); + if(state.messages == null) { + fetchChatMessages(streamId); + }else { + try { + final messages = await ChatHandlers(_grpcHandler).getChatMessages(streamId); + final combinedMessages = List.from(state.messages ?? []) + ..addAll(messages.where((newMessage) => !state.messages!.contains(newMessage))); + state = state.copyWith(messages: combinedMessages, isLoading: false, accessDenied: false); + } catch (e) { + state = state.copyWith( + error: e as AppError, + isLoading: false, + accessDenied: true, + ); + } + } + } + Future postChatMessage(int streamId, String message) async { try { fetchChatMessages(streamId); diff --git a/lib/view_models/setting_view_model.dart b/lib/view_models/setting_view_model.dart index 4a7f9390..4df36444 100644 --- a/lib/view_models/setting_view_model.dart +++ b/lib/view_models/setting_view_model.dart @@ -120,4 +120,5 @@ class SettingViewModel extends StateNotifier { void setLoading(bool isLoading) { state = state.copyWith(isLoading: isLoading); } + } diff --git a/lib/views/chat_view/chat_view_state.dart b/lib/views/chat_view/chat_view_state.dart index 37601d35..d11eb412 100644 --- a/lib/views/chat_view/chat_view_state.dart +++ b/lib/views/chat_view/chat_view_state.dart @@ -8,6 +8,7 @@ import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/views/chat_view/chat_view.dart'; import 'package:logger/logger.dart'; + class ChatViewState extends ConsumerState { late ScrollController _scrollController; Timer? _updateTimer; diff --git a/lib/views/chat_view/inactive_view.dart b/lib/views/chat_view/inactive_view.dart index 3cb8aeeb..7cf7f08d 100644 --- a/lib/views/chat_view/inactive_view.dart +++ b/lib/views/chat_view/inactive_view.dart @@ -5,6 +5,8 @@ import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/views/chat_view/suggested_streams_list.dart'; import 'package:gocast_mobile/views/video_view/video_player.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class InactiveView extends ConsumerStatefulWidget { final int? streamID; @@ -62,8 +64,8 @@ class InactiveViewState extends ConsumerState { ), child: Text( chatState.accessDenied - ? 'Chat is disabled for this lecture' - : 'Chat is Hidden', + ? AppLocalizations.of(context)!.chat_is_disabled_for_this_lecture + : AppLocalizations.of(context)!.chat_is_hidden, textAlign: TextAlign.center, style: const TextStyle( color: Colors.black, diff --git a/lib/views/chat_view/poll_view_state.dart b/lib/views/chat_view/poll_view_state.dart index 7e1ee976..c71b7e4c 100644 --- a/lib/views/chat_view/poll_view_state.dart +++ b/lib/views/chat_view/poll_view_state.dart @@ -5,6 +5,7 @@ import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/views/chat_view/poll_view.dart'; + class PollViewState extends ConsumerState { Timer? _updateTimer; Map selectedOptions = {}; @@ -77,7 +78,6 @@ class PollViewState extends ConsumerState { ThemeData themeData = Theme.of(context); return Opacity( opacity: 0.5, - // You might want to adjust this value based on your design needs child: Card( elevation: 1, shadowColor: themeData.shadowColor, @@ -103,11 +103,10 @@ class PollViewState extends ConsumerState { GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - // to disable GridView's scrolling gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: - 3, // Adjust the aspect ratio to fit the content + 3, ), itemCount: poll.pollOptions.length, itemBuilder: (context, index) { @@ -127,20 +126,19 @@ class PollViewState extends ConsumerState { PollOption option, int? selectedOptionId,) { bool isSelected = option.id == selectedOptionId; return Container( - margin: const EdgeInsets.all(4.0), // Add some spacing around each button + margin: const EdgeInsets.all(4.0), decoration: BoxDecoration( color: isSelected ? Colors.grey : Colors.white, - // Use grey for the selected option, white for others borderRadius: BorderRadius.circular(8.0), border: - Border.all(color: Colors.grey), // Use grey border for all options + Border.all(color: Colors.grey), ), child: Center( child: Text( option.answer, style: const TextStyle( color: - Colors.black, // Text color remains black to ensure readability + Colors.black, ), ), ), @@ -173,11 +171,10 @@ class PollViewState extends ConsumerState { GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - // to disable GridView's scrolling gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: - 3, // Adjust the aspect ratio to fit the content + 3, ), itemCount: poll.pollOptions.length, itemBuilder: (context, index) { @@ -228,22 +225,19 @@ class PollViewState extends ConsumerState { return Padding( padding: const EdgeInsets.all(8.0), child: Row( - // Center the question since there's no subtitle or trailing widget children: [ Expanded( child: Text( poll.question, style: const TextStyle( - fontSize: 16.0, // Match the font size used in _buildHeader + fontSize: 16.0, fontWeight: FontWeight - .bold, // Match the font weight used in _buildHeader + .bold, ), textAlign: TextAlign.center, - // Keep text alignment to center as it's a question maxLines: 2, - // Optional: Use if you want to limit the number of lines for the question overflow: TextOverflow - .ellipsis, // Optional: Use to handle text overflow + .ellipsis, ), ), ], @@ -269,18 +263,18 @@ class PollViewState extends ConsumerState { } : null, style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, // Use the primary color of your theme - foregroundColor: Colors.white, // Text color is white for better contrast + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), // Less rounded corners for a more rectangular look + borderRadius: BorderRadius.circular(8.0), ), - padding: const EdgeInsets.symmetric(vertical: 14.0, horizontal: 30.0), // Adjust padding to control the button's shape - minimumSize: const Size(double.infinity, 48), // Ensuring full width and a consistent height - elevation: 2, // Slight elevation for a subtle shadow, adjust as needed + padding: const EdgeInsets.symmetric(vertical: 14.0, horizontal: 30.0), + minimumSize: const Size(double.infinity, 48), + elevation: 2, ), child: const Text( 'Submit', - style: TextStyle(fontSize: 16), // Adjust font size as needed + style: TextStyle(fontSize: 16), ), ), ); diff --git a/lib/views/components/custom_bottom_nav_bar.dart b/lib/views/components/custom_bottom_nav_bar.dart index e2432a7e..b3b70486 100644 --- a/lib/views/components/custom_bottom_nav_bar.dart +++ b/lib/views/components/custom_bottom_nav_bar.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class CustomBottomNavBar extends ConsumerWidget { const CustomBottomNavBar({super.key}); @@ -25,28 +27,28 @@ class CustomBottomNavBar extends ConsumerWidget { items: [ _buildNavigationBarItem( Icons.home, - 'Home', + AppLocalizations.of(context)!.home, context, currentIndex, 0, ), _buildNavigationBarItem( Icons.download, - 'Downloads', + AppLocalizations.of(context)!.downloads, context, currentIndex, 1, ), _buildNavigationBarItem( Icons.push_pin, - 'Pinned', + AppLocalizations.of(context)!.pinned, context, currentIndex, 2, ), _buildNavigationBarItem( Icons.notifications, - 'Notifications', + AppLocalizations.of(context)!.notifications, context, currentIndex, 3, diff --git a/lib/views/components/custom_search_filter_top_nav_bar.dart b/lib/views/components/custom_search_filter_top_nav_bar.dart index 03c1f593..78cbf464 100644 --- a/lib/views/components/custom_search_filter_top_nav_bar.dart +++ b/lib/views/components/custom_search_filter_top_nav_bar.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class CustomSearchFilterTopNavBar extends StatelessWidget implements PreferredSizeWidget { @@ -32,11 +34,11 @@ class CustomSearchFilterTopNavBar extends StatelessWidget ), child: TextField( controller: searchController, - decoration: const InputDecoration( + decoration: InputDecoration( border: InputBorder.none, - hintText: 'Search', - prefixIcon: Icon(Icons.search, color: Color(0x993C3C43)), - hintStyle: TextStyle( + hintText: AppLocalizations.of(context)!.search, + prefixIcon: const Icon(Icons.search, color: Color(0x993C3C43)), + hintStyle: const TextStyle( color: Color(0x993C3C43), fontSize: 17, fontFamily: 'SF Pro Text', diff --git a/lib/views/components/custom_search_top_nav_bar.dart b/lib/views/components/custom_search_top_nav_bar.dart index 3a898f82..4411a5b5 100644 --- a/lib/views/components/custom_search_top_nav_bar.dart +++ b/lib/views/components/custom_search_top_nav_bar.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/views/components/filter_popup_menu_button.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class CustomSearchTopNavBar extends ConsumerWidget implements PreferredSizeWidget { @@ -98,7 +100,7 @@ class CustomSearchTopNavBar extends ConsumerWidget onChanged: (value) {}, decoration: InputDecoration( border: InputBorder.none, - hintText: 'Search', + hintText: AppLocalizations.of(context)!.search, prefixIcon: Icon( Icons.search, color: Theme.of(context).inputDecorationTheme.hintStyle?.color, diff --git a/lib/views/components/custom_search_top_nav_bar_back_button.dart b/lib/views/components/custom_search_top_nav_bar_back_button.dart index e6fbae58..097603bf 100644 --- a/lib/views/components/custom_search_top_nav_bar_back_button.dart +++ b/lib/views/components/custom_search_top_nav_bar_back_button.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/views/components/filter_popup_menu_button.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class CustomSearchTopNavBarWithBackButton extends ConsumerWidget implements PreferredSizeWidget { @@ -61,7 +63,7 @@ class CustomSearchTopNavBarWithBackButton extends ConsumerWidget controller: searchController, decoration: InputDecoration( border: InputBorder.none, - hintText: 'Search', + hintText: AppLocalizations.of(context)!.search, prefixIcon: Icon( Icons.search, color: Theme.of(context).inputDecorationTheme.hintStyle?.color, diff --git a/lib/views/course_view/components/base_card.dart b/lib/views/course_view/components/base_card.dart deleted file mode 100644 index c32f43d5..00000000 --- a/lib/views/course_view/components/base_card.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gocast_mobile/utils/theme.dart'; - -class BaseCard extends StatelessWidget { - final String imageName; - final VoidCallback onTap; - - const BaseCard({ - super.key, - required this.imageName, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - ThemeData themeData = Theme.of(context); - - return InkWell( - onTap: onTap, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ), - elevation: 5, - color: themeData.cardTheme.color, - shadowColor: themeData.shadowColor, - clipBehavior: Clip.antiAlias, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: buildCardContent(), - ), - ), - ); - } - - @protected - List buildCardContent() { - return []; - } - - @protected - Widget buildHeader({ - required String title, - required String subtitle, - Widget? trailing, - }) { - return Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - color: appTheme.cardTheme.color, - ), - maxLines: 2, - ), - const SizedBox(height: 8.0), - Text( - subtitle, - style: TextStyle(fontSize: 14.0, color: Colors.grey[600]), - ), - ], - ), - ), - if (trailing != null) trailing, - ], - ), - ); - } - - Widget buildInfo(String courseNameAndSlug, String courseDetails) { - return Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - courseNameAndSlug, - style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), - maxLines: 2, - ), - const SizedBox(height: 8.0), - Text( - courseDetails, - style: const TextStyle(fontSize: 14.0, color: Colors.grey), - ), - ], - ), - ); - } - - Widget buildInternetImage() { - return AspectRatio( - aspectRatio: 16 / 9, - child: Image.network( - imageName, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Image.asset( - 'assets/images/course1.png', - fit: BoxFit.cover, - ); - }, - ), - ); - } - - Widget buildImage() { - return AspectRatio( - aspectRatio: 16 / 9, - child: Image.asset( - imageName, - fit: BoxFit.cover, - ), - ); - } -} diff --git a/lib/views/course_view/components/course_card.dart b/lib/views/course_view/components/course_card.dart index cc3313c7..fb24f71e 100644 --- a/lib/views/course_view/components/course_card.dart +++ b/lib/views/course_view/components/course_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class CourseCard extends StatelessWidget { final String title; @@ -101,7 +102,7 @@ class CourseCard extends StatelessWidget { backgroundColor: Colors.red, foregroundColor: Colors.white, icon: Icons.push_pin_outlined, - label: 'Unpin', + label: AppLocalizations.of(context)!.unpin , ), if (!isPinned) SlidableAction( @@ -110,7 +111,7 @@ class CourseCard extends StatelessWidget { backgroundColor: Colors.blue, foregroundColor: Colors.white, icon: Icons.push_pin, - label: 'Pin', + label: AppLocalizations.of(context)!.pin, ), ], ) : null, @@ -129,7 +130,7 @@ class CourseCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildCourseTumID(), - _buildCourseIsLive(), + _buildCourseIsLive(context), ], ), Padding( @@ -151,17 +152,16 @@ class CourseCard extends StatelessWidget { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirm Unpin'), - content: - const Text('Are you sure you want to unpin this course?'), + title: Text(AppLocalizations.of(context)!.confirm_unpin_title), + content: Text(AppLocalizations.of(context)!.confirm_unpin_message), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: const Text('Unpin'), + child: Text(AppLocalizations.of(context)!.unpin), ), ], ); @@ -174,20 +174,20 @@ class CourseCard extends StatelessWidget { - Widget _buildCourseIsLive() { + Widget _buildCourseIsLive(BuildContext context) { if (live == null) return const SizedBox(); return live! - ? const Row( + ? Row( children: [ - Icon( + const Icon( Icons.circle, size: 10, color: Colors.red, ), - SizedBox(width: 5), + const SizedBox(width: 5), Text( - 'Live Now', - style: TextStyle( + AppLocalizations.of(context)!.live_now, + style: const TextStyle( color: Colors.red, fontWeight: FontWeight.bold, ), diff --git a/lib/views/course_view/components/pulse_background.dart b/lib/views/course_view/components/pulse_background.dart index 6674b04d..6cfca0d7 100644 --- a/lib/views/course_view/components/pulse_background.dart +++ b/lib/views/course_view/components/pulse_background.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class PulsingBackground extends StatefulWidget { const PulsingBackground({super.key}); @@ -42,7 +44,7 @@ class _PulsingBackgroundState extends State borderRadius: BorderRadius.circular(4.0), ), child: Text( - 'Live Now', + AppLocalizations.of(context)!.live_now, style: Theme.of(context) .textTheme .titleMedium diff --git a/lib/views/course_view/components/small_stream_card.dart b/lib/views/course_view/components/small_stream_card.dart index dbf271d9..70c20b0c 100644 --- a/lib/views/course_view/components/small_stream_card.dart +++ b/lib/views/course_view/components/small_stream_card.dart @@ -3,6 +3,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/utils/constants.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SmallStreamCard extends StatelessWidget { final String title; @@ -65,6 +66,7 @@ class SmallStreamCard extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: _buildStreamCard( + context, themeData, cardWidth, ), @@ -73,11 +75,11 @@ class SmallStreamCard extends StatelessWidget { ); } - Widget _buildStreamCard(ThemeData themeData, double cardWidth) { - return (isDownloaded!=null && showDeleteConfirmationDialog!=null) ? _buildDownloadedCard(themeData, cardWidth) : _buildLiveCard(themeData, cardWidth); + Widget _buildStreamCard(BuildContext context, ThemeData themeData, double cardWidth) { + return (isDownloaded!=null && showDeleteConfirmationDialog!=null) ? _buildDownloadedCard(context, themeData, cardWidth) : _buildLiveCard(themeData, cardWidth); } - Widget _buildDownloadedCard (ThemeData themeData, double cardWidth) { + Widget _buildDownloadedCard (BuildContext context, ThemeData themeData, double cardWidth) { return Slidable( key: Key(courseId.toString()), closeOnScroll: true, @@ -91,7 +93,7 @@ class SmallStreamCard extends StatelessWidget { backgroundColor: Colors.red, foregroundColor: Colors.white, icon: Icons.delete_rounded, - label: 'Delete', + label: AppLocalizations.of(context)!.delete, ), ], ), diff --git a/lib/views/course_view/course_detail_view/course_detail_view.dart b/lib/views/course_view/course_detail_view/course_detail_view.dart index 61422703..a9e8078b 100644 --- a/lib/views/course_view/course_detail_view/course_detail_view.dart +++ b/lib/views/course_view/course_detail_view/course_detail_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pbgrpc.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:gocast_mobile/views/components/custom_search_top_nav_bar_back_button.dart'; import 'package:gocast_mobile/views/course_view/components/pin_button.dart'; @@ -87,7 +88,7 @@ class CourseDetailState extends ConsumerState { appBar: CustomSearchTopNavBarWithBackButton( searchController: searchController, onClick: _handleSortOptionSelected, - filterOptions: const ['Newest First', 'Oldest First'], + filterOptions: [AppLocalizations.of(context)!.newest_first, AppLocalizations.of(context)!.oldest_first], ), body: RefreshIndicator( onRefresh: () => _refreshStreams(widget.courseId), @@ -166,7 +167,7 @@ class CourseDetailState extends ConsumerState { ), ), ) - : const Center(child: Text('No courses available')), + : Center(child: Text(AppLocalizations.of(context)!.no_courses_found)), ); } diff --git a/lib/views/course_view/courses_overview.dart b/lib/views/course_view/courses_overview.dart index 02993f60..d95aadee 100644 --- a/lib/views/course_view/courses_overview.dart +++ b/lib/views/course_view/courses_overview.dart @@ -8,8 +8,8 @@ import 'package:gocast_mobile/views/course_view/components/live_stream_section.d import 'package:gocast_mobile/views/course_view/list_courses_view/my_courses_view.dart'; import 'package:gocast_mobile/views/course_view/list_courses_view/public_courses_view.dart'; import 'package:gocast_mobile/views/settings_view/settings_screen_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -// current index of the bottom navigation bar (0 = My Courses, 1 = Public Courses) class CourseOverview extends ConsumerStatefulWidget { const CourseOverview({super.key}); @@ -75,7 +75,7 @@ class CourseOverviewState extends ConsumerState { Center( child: LiveStreamSection( ref: ref, - sectionTitle: "Live Now", + sectionTitle: AppLocalizations.of(context)!.live_now, courses: (userCourses) + (publicCourses), streams: liveStreamWithThumb, ), @@ -86,7 +86,7 @@ class CourseOverviewState extends ConsumerState { children: [ Expanded( child: _buildSection( - "My Courses", + AppLocalizations.of(context)!.my_courses, SectionKind.myCourses, userCourses, liveStreams, @@ -94,7 +94,7 @@ class CourseOverviewState extends ConsumerState { ), Expanded( child: _buildSection( - "Public Courses", + AppLocalizations.of(context)!.public_courses, SectionKind.publicCourses, publicCourses, liveStreams, @@ -104,13 +104,13 @@ class CourseOverviewState extends ConsumerState { ) else ...[ _buildSection( - "My Courses", + AppLocalizations.of(context)!.my_courses, SectionKind.myCourses, userCourses, liveStreams, ), _buildSection( - "Public Courses", + AppLocalizations.of(context)!.public_courses, SectionKind.publicCourses, publicCourses, liveStreams, @@ -131,21 +131,23 @@ class CourseOverviewState extends ConsumerState { ref: ref, sectionTitle: title, sectionKind: sectionKind, - onViewAll: () => _onViewAllPressed(title), + onViewAll: () => _onViewAllPressed(sectionKind), courses: courses, streams: streams, ); } - void _onViewAllPressed(String title) { - switch (title) { - case "My Courses": + void _onViewAllPressed(SectionKind sectionKind) { + switch (sectionKind) { + case SectionKind.myCourses: _navigateTo(const MyCourses()); break; - case "Public Courses": + case SectionKind.publicCourses: _navigateTo(const PublicCourses()); break; // Add more cases as needed + case SectionKind.livestreams: + break; } } diff --git a/lib/views/course_view/downloaded_courses_view/download_card.dart b/lib/views/course_view/downloaded_courses_view/download_card.dart deleted file mode 100644 index 2d4b6205..00000000 --- a/lib/views/course_view/downloaded_courses_view/download_card.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gocast_mobile/views/course_view/components/base_card.dart'; - -class VideoCard extends BaseCard { - final String title; - final String date; - final String duration; - final VoidCallback onDelete; - - const VideoCard({ - super.key, - required this.title, - required this.date, - required this.duration, - required super.imageName, - required super.onTap, - required this.onDelete, - }); - - @override - List buildCardContent() { - return [ - buildHeader( - title: title, - subtitle: date, - trailing: IconButton( - icon: const Icon(Icons.delete), - onPressed: onDelete, - ), - ), - buildImage(), // Assuming this method exists in BaseCard for displaying the image - // Additional content can be added here if needed - ]; - } -} diff --git a/lib/views/course_view/downloaded_courses_view/download_content_view.dart b/lib/views/course_view/downloaded_courses_view/download_content_view.dart index 3065c052..b0ac65e8 100644 --- a/lib/views/course_view/downloaded_courses_view/download_content_view.dart +++ b/lib/views/course_view/downloaded_courses_view/download_content_view.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gocast_mobile/utils/constants.dart'; import 'package:gocast_mobile/views/components/base_view.dart'; import 'package:gocast_mobile/views/components/custom_search_top_nav_bar.dart'; import 'package:gocast_mobile/views/course_view/components/small_stream_card.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import '../../../utils/constants.dart'; /// CourseListScreen /// @@ -42,13 +43,13 @@ class DownloadCoursesContentView extends ConsumerWidget { itemCount: videoCards.isEmpty ? 1 : videoCards.length, itemBuilder: (BuildContext context, int index) { if (videoCards.isEmpty) { - return const Center( + return Center( child: Padding( padding: AppPadding.sectionPadding, child: Center( child: Padding( - padding: EdgeInsets.symmetric(vertical: 295.0), - child: Text('No Downloaded Courses'), + padding: const EdgeInsets.symmetric(vertical: 295.0), + child: Text(AppLocalizations.of(context)!.no_downloaded_courses), ), ), ), diff --git a/lib/views/course_view/downloaded_courses_view/downloaded_courses_view.dart b/lib/views/course_view/downloaded_courses_view/downloaded_courses_view.dart index e689063d..7c1bbb09 100644 --- a/lib/views/course_view/downloaded_courses_view/downloaded_courses_view.dart +++ b/lib/views/course_view/downloaded_courses_view/downloaded_courses_view.dart @@ -6,6 +6,8 @@ import 'package:gocast_mobile/views/components/custom_search_top_nav_bar.dart'; import 'package:gocast_mobile/views/course_view/components/small_stream_card.dart'; import 'package:gocast_mobile/views/course_view/downloaded_courses_view/download_content_view.dart'; import 'package:gocast_mobile/views/video_view/offline_video_player/offline_video_player.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class DownloadedCourses extends ConsumerStatefulWidget { const DownloadedCourses({super.key}); @@ -22,17 +24,17 @@ class DownloadedCoursesState extends ConsumerState { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirm Deletion'), - content: const Text('Are you sure you want to delete this video?'), + title: Text(AppLocalizations.of(context)!.confirm_delete), + content: Text(AppLocalizations.of(context)!.confirm_delete_message), actions: [ TextButton( - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), onPressed: () { Navigator.of(context).pop(); // Dismiss the dialog }, ), TextButton( - child: const Text('Delete'), + child: Text(AppLocalizations.of(context)!.delete), onPressed: () async { Navigator.of(context).pop(); // Dismiss the dialog await ref @@ -63,8 +65,8 @@ class DownloadedCoursesState extends ConsumerState { child: DownloadCoursesContentView( customAppBar: CustomSearchTopNavBar( searchController: searchController, - title: 'Downloads', - filterOptions: const ['Newest First', 'Oldest First'], + title: AppLocalizations.of(context)!.download, + filterOptions: [AppLocalizations.of(context)!.newest_first, AppLocalizations.of(context)!.oldest_first], onClick: (String choice) { // Handle filter option click }, @@ -98,9 +100,4 @@ class DownloadedCoursesState extends ConsumerState { ); } -} - - -// onDelete: () { -// _showDeleteConfirmationDialog(videoId); -// }, \ No newline at end of file +} \ No newline at end of file diff --git a/lib/views/course_view/list_courses_view/courses_list_view.dart b/lib/views/course_view/list_courses_view/courses_list_view.dart index 3d338d02..2b5b24ea 100644 --- a/lib/views/course_view/list_courses_view/courses_list_view.dart +++ b/lib/views/course_view/list_courses_view/courses_list_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pbgrpc.dart'; import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/utils/constants.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:gocast_mobile/views/course_view/components/course_card.dart'; import 'package:gocast_mobile/views/course_view/course_detail_view/course_detail_view.dart'; @@ -29,14 +30,14 @@ class CoursesList extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { bool isTablet = MediaQuery.of(context).size.width >= 600; return courses.isEmpty - ? _buildPlaceholder() + ? _buildPlaceholder(context) : _buildCourseListView(context, isTablet, ref); } - Padding _buildPlaceholder() { - return const Padding( + Padding _buildPlaceholder(BuildContext context) { + return Padding( padding: AppPadding.sectionPadding, - child: Center(child: Text('No courses found.')), + child: Center(child: Text(AppLocalizations.of(context)!.no_courses_found)), ); } diff --git a/lib/views/course_view/list_courses_view/my_courses_view.dart b/lib/views/course_view/list_courses_view/my_courses_view.dart index 1ea7ef97..bab0da0e 100644 --- a/lib/views/course_view/list_courses_view/my_courses_view.dart +++ b/lib/views/course_view/list_courses_view/my_courses_view.dart @@ -5,6 +5,7 @@ import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pbgrpc.dart'; import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/views/components/custom_search_top_nav_bar_back_button.dart'; import 'package:gocast_mobile/views/course_view/list_courses_view/courses_list_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class MyCourses extends ConsumerStatefulWidget { const MyCourses({super.key}); @@ -86,7 +87,7 @@ class MyCoursesState extends ConsumerState { dragStartBehavior: DragStartBehavior.down, children: [ CoursesList( - title: 'My Courses', + title: AppLocalizations.of(context)!.my_courses, courses: myCourses, ), ], diff --git a/lib/views/course_view/list_courses_view/public_courses_view.dart b/lib/views/course_view/list_courses_view/public_courses_view.dart index c9893ade..31c913d9 100644 --- a/lib/views/course_view/list_courses_view/public_courses_view.dart +++ b/lib/views/course_view/list_courses_view/public_courses_view.dart @@ -5,6 +5,7 @@ import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pbgrpc.dart'; import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/views/components/custom_search_top_nav_bar_back_button.dart'; import 'package:gocast_mobile/views/course_view/list_courses_view/courses_list_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class PublicCourses extends ConsumerStatefulWidget { const PublicCourses({super.key}); @@ -89,7 +90,7 @@ class PublicCoursesState extends ConsumerState { children: [ CoursesList( courses: publicCourses, - title: 'Public Courses', + title: AppLocalizations.of(context)!.public_courses, ), ], ), diff --git a/lib/views/course_view/pinned_courses_view/pinned_courses_content_view.dart b/lib/views/course_view/pinned_courses_view/pinned_courses_content_view.dart index 6f4a83a5..5c44ad92 100644 --- a/lib/views/course_view/pinned_courses_view/pinned_courses_content_view.dart +++ b/lib/views/course_view/pinned_courses_view/pinned_courses_content_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/utils/constants.dart'; import 'package:gocast_mobile/views/components/base_view.dart'; import 'package:gocast_mobile/views/components/custom_search_top_nav_bar.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:gocast_mobile/views/course_view/components/course_card.dart'; @@ -42,7 +43,7 @@ class PinnedCoursesContentView extends ConsumerWidget { itemCount: pinnedCourseCards.isEmpty ? 1 : pinnedCourseCards.length, itemBuilder: (BuildContext context, int index) { if (pinnedCourseCards.isEmpty) { - return const Center( + return Center( child: Padding( padding: AppPadding.sectionPadding, child: Center( @@ -50,8 +51,8 @@ class PinnedCoursesContentView extends ConsumerWidget { padding: AppPadding.sectionPadding, child: Center( child: Padding( - padding: EdgeInsets.symmetric(vertical: 295.0), - child: Text('No Pinned Courses'), + padding: const EdgeInsets.symmetric(vertical: 295.0), + child: Text(AppLocalizations.of(context)!.pinned_empty,), ), ), ), diff --git a/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart b/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart index d52feec6..d03e8835 100644 --- a/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart +++ b/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart @@ -8,6 +8,7 @@ import 'package:gocast_mobile/views/course_view/components/course_card.dart'; import 'package:gocast_mobile/views/course_view/course_detail_view/course_detail_view.dart'; import 'package:gocast_mobile/views/course_view/pinned_courses_view/pinned_courses_content_view.dart'; import 'package:gocast_mobile/views/video_view/video_player_controller.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class PinnedCourses extends ConsumerStatefulWidget { const PinnedCourses({super.key}); @@ -86,7 +87,7 @@ class PinnedCoursesState extends ConsumerState { child: PinnedCoursesContentView( customAppBar: CustomSearchTopNavBar( searchController: searchController, - title: "Pinned Courses", + title: AppLocalizations.of(context)!.pinned_courses, filterOptions: filterOptions, onClick: filterCoursesBySemester, ), diff --git a/lib/views/login_view/internal_login_view.dart b/lib/views/login_view/internal_login_view.dart index 981c5d37..61ba4fad 100644 --- a/lib/views/login_view/internal_login_view.dart +++ b/lib/views/login_view/internal_login_view.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + /// Internal login screen view. /// @@ -39,21 +41,21 @@ class InternalLoginScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildWelcomeText(), + _buildWelcomeText(context), const SizedBox(height: 48), _buildTextField( - 'Username', + AppLocalizations.of(context)!.username, 'e.g. go42tum / example@tum.de', usernameController, ), const SizedBox(height: 24), _buildTextField( - 'Password', - 'Enter your password', + AppLocalizations.of(context)!.password, + AppLocalizations.of(context)!.enter_your_password, passwordController, obscureText: true, ), - _buildForgotPasswordButton(), + _buildForgotPasswordButton(context), const SizedBox(height: 24), _buildLoginButton(context, ref), ], @@ -64,9 +66,9 @@ class InternalLoginScreenState extends ConsumerState { ); } - Widget _buildWelcomeText() { + Widget _buildWelcomeText(BuildContext context) { return Text( - 'Welcome To GoCast!', + AppLocalizations.of(context)!.welcome_to_gocast, textAlign: TextAlign.center, style: TextStyle( color: Colors.blue[900], @@ -101,14 +103,14 @@ class InternalLoginScreenState extends ConsumerState { ); } - Widget _buildForgotPasswordButton() { + Widget _buildForgotPasswordButton(BuildContext context) { return TextButton( onPressed: () { // TODO: Forgot Password action }, - child: const Text( - 'Forgot Password?', - style: TextStyle(color: Colors.blue), + child: Text( + AppLocalizations.of(context)!.forgot_password, + style: const TextStyle(color: Colors.blue), ), ); } diff --git a/lib/views/notifications_view/notifications_overview.dart b/lib/views/notifications_view/notifications_overview.dart index 39632854..2a053b47 100644 --- a/lib/views/notifications_view/notifications_overview.dart +++ b/lib/views/notifications_view/notifications_overview.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; import 'notifications_screen_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class MyNotifications extends ConsumerStatefulWidget { const MyNotifications({super.key}); @@ -33,7 +35,7 @@ class MyNotificationsState extends ConsumerState { final bannerAlerts = notificationState.bannerAlerts ?? []; return NotificationsScreen( - title: 'Notifications', + title: AppLocalizations.of(context)!.notifications, pushNotifications: pushNotifications, featureNotifications: featureNotifications, bannerAlerts: bannerAlerts, diff --git a/lib/views/notifications_view/notifications_screen_view.dart b/lib/views/notifications_view/notifications_screen_view.dart index 42c44bfb..00ce7c03 100644 --- a/lib/views/notifications_view/notifications_screen_view.dart +++ b/lib/views/notifications_view/notifications_screen_view.dart @@ -5,6 +5,8 @@ import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/models/notifications/push_notification.dart'; import 'package:gocast_mobile/utils/constants.dart'; import 'package:gocast_mobile/views/settings_view/settings_screen_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class NotificationsScreen extends ConsumerWidget { final String title; @@ -39,12 +41,12 @@ class NotificationsScreen extends ConsumerWidget { physics: const AlwaysScrollableScrollPhysics(), scrollDirection: Axis.vertical, children: [ - _buildSectionHeader('Banner Alerts'), + _buildSectionHeader(AppLocalizations.of(context)!.banner_notification), for (var alert in bannerAlerts) _buildBannerAlert(alert), - _buildSectionHeader('Feature Notifications'), + _buildSectionHeader(AppLocalizations.of(context)!.feature_notifications), for (var notification in featureNotifications) _buildFeatureNotification(notification), - _buildSectionHeader('Recent Uploads'), + _buildSectionHeader(AppLocalizations.of(context)!.recent_uploads), for (var notification in pushNotifications) _buildPushNotification(notification), ], @@ -66,12 +68,12 @@ class NotificationsScreen extends ConsumerWidget { MediaQuery.of(context).padding.bottom - kToolbarHeight, ), - child: const IntrinsicHeight( + child: IntrinsicHeight( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( - child: Center(child: Text('No Notifications found.')), + child: Center(child: Text(AppLocalizations.of(context)!.no_notifications_found)), ), ], ), diff --git a/lib/views/on_boarding_view/enable_notification_view.dart b/lib/views/on_boarding_view/enable_notification_view.dart index 67765c99..e6b62868 100644 --- a/lib/views/on_boarding_view/enable_notification_view.dart +++ b/lib/views/on_boarding_view/enable_notification_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class EnableNotificationscreen extends StatelessWidget { const EnableNotificationscreen({super.key}); @@ -20,20 +22,20 @@ class EnableNotificationscreen extends StatelessWidget { height: 200.0, ), const SizedBox(height: 24), - const Text( - 'Turn on Notifications ?', + Text( + AppLocalizations.of(context)!.turn_on_notifications, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black, ), ), const SizedBox(height: 8), - const Text( - "Receive instant updates on live lectures, upcoming quizzes, and important announcements. Never miss a beat in your academic journey.", + Text( + AppLocalizations.of(context)!.notifications_description, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( fontSize: 16, color: Colors.black54, ), @@ -49,16 +51,16 @@ class EnableNotificationscreen extends StatelessWidget { borderRadius: BorderRadius.circular(15.0), ), ), - child: const Text( - 'Enable notifications', - style: TextStyle(fontSize: 18), + child: Text( + AppLocalizations.of(context)!.enable_notifications, + style: const TextStyle(fontSize: 18), ), onPressed: () {}, ), const SizedBox(height: 12), TextButton( child: Text( - 'Skip', + AppLocalizations.of(context)!.skip, style: TextStyle(fontSize: 18, color: Colors.blue[900]), ), onPressed: () {}, diff --git a/lib/views/on_boarding_view/welcome_screen_view.dart b/lib/views/on_boarding_view/welcome_screen_view.dart index 12dafb31..bfb12ba9 100644 --- a/lib/views/on_boarding_view/welcome_screen_view.dart +++ b/lib/views/on_boarding_view/welcome_screen_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/utils/constants.dart'; import 'package:gocast_mobile/views/login_view/internal_login_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; /// Welcome screen view. /// This is the first screen that the user sees when the app is opened. @@ -44,9 +45,9 @@ class WelcomeScreen extends ConsumerWidget { const Spacer(), _buildLogo(screenSize), SizedBox(height: screenSize.height * 0.03), - _buildWelcomeText(), + _buildWelcomeText(context), const SizedBox(height: 8), - _buildOverviewText(), + _buildOverviewText(context), const Spacer(), _buildLoginButton(context, ref), const SizedBox(height: 12), @@ -73,9 +74,9 @@ class WelcomeScreen extends ConsumerWidget { children: [ _buildLogo(screenSize), const SizedBox(height: 24), - _buildWelcomeText(), + _buildWelcomeText(context), const SizedBox(height: 8), - _buildOverviewText(), + _buildOverviewText(context), const SizedBox(height: 48), _buildLoginButton(context, ref), const SizedBox(height: 12), @@ -103,11 +104,11 @@ class WelcomeScreen extends ConsumerWidget { ); } - Widget _buildWelcomeText() { - return const Text( - 'Welcome to Gocast', + Widget _buildWelcomeText(BuildContext context) { + return Text( + AppLocalizations.of(context)!.welcome_to_gocast, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF0D47A1), @@ -115,11 +116,11 @@ class WelcomeScreen extends ConsumerWidget { ); } - Widget _buildOverviewText() { - return const Text( - "Your Lectures on the Go", + Widget _buildOverviewText(BuildContext context) { + return Text( + AppLocalizations.of(context)!.your_lectures_on_the_go, textAlign: TextAlign.center, - style: TextStyle(fontSize: 16, color: Colors.black54), + style: const TextStyle(fontSize: 16, color: Colors.black54), ); } @@ -143,7 +144,7 @@ class WelcomeScreen extends ConsumerWidget { valueColor: AlwaysStoppedAnimation(Colors.white), ), ) - : const Text('TUM Login', style: TextStyle(fontSize: 18)), + : Text(AppLocalizations.of(context)!.tum_login, style: const TextStyle(fontSize: 18)), onPressed: () => handleSSOLogin(context, ref), ); } @@ -159,9 +160,8 @@ class WelcomeScreen extends ConsumerWidget { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), ), - child: const Text('Continue without', style: TextStyle(fontSize: 18)), + child: Text(AppLocalizations.of(context)!.continue_without, style: const TextStyle(fontSize: 18)), onPressed: () { - //TODO: Continue without login action Navigator.pushNamed(context, '/publiccourses'); }, ); @@ -173,10 +173,10 @@ class WelcomeScreen extends ConsumerWidget { context, MaterialPageRoute(builder: (context) => const InternalLoginScreen()), ), - child: const Center( + child: Center( child: Text( - 'Use an internal account', - style: TextStyle( + AppLocalizations.of(context)!.use_an_internal_account, + style: const TextStyle( decoration: TextDecoration.underline, color: Colors.grey, decorationColor: Colors.grey, diff --git a/lib/views/settings_view/authentication_error_card_view.dart b/lib/views/settings_view/authentication_error_card_view.dart index b0aa1e41..420ca3b7 100644 --- a/lib/views/settings_view/authentication_error_card_view.dart +++ b/lib/views/settings_view/authentication_error_card_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; Future showAuthenticationErrorCard( BuildContext context, @@ -18,8 +19,8 @@ Future showAuthenticationErrorCard( borderRadius: BorderRadius.circular(12.0), ), backgroundColor: Theme.of(context).colorScheme.onPrimary, - title: const Text('Authentication Required'), - content: const Text('Please log in to access this feature.'), + title: Text(AppLocalizations.of(context)!.auth_required_title), + content: Text(AppLocalizations.of(context)!.auth_required_message), actions: [ TextButton( child: const Text('OK'), diff --git a/lib/views/settings_view/custom_playback_speed_view.dart b/lib/views/settings_view/custom_playback_speed_view.dart index 61821214..ce39776f 100644 --- a/lib/views/settings_view/custom_playback_speed_view.dart +++ b/lib/views/settings_view/custom_playback_speed_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + void showAddCustomSpeedDialog( BuildContext context, @@ -19,12 +21,12 @@ void showAddCustomSpeedDialog( List splitValue = value.split('.'); if ((splitValue[0].length > 1) || (splitValue.length > 1 && splitValue[1].length > 2)) { - errorMessage = 'Number is too long'; + errorMessage = AppLocalizations.of(context)!.number_too_long; } else { customSpeed = parsedValue; } } else { - errorMessage = 'Please enter a number between\n0.25 and 2.0'; + errorMessage = AppLocalizations.of(context)!.enter_number_between; } } } @@ -40,13 +42,13 @@ void showAddCustomSpeedDialog( borderRadius: BorderRadius.circular(12.0), ), backgroundColor: Theme.of(context).colorScheme.onPrimary, - title: const Text('Add Custom Speed'), + title: Text(AppLocalizations.of(context)!.add_custom_speed), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( decoration: InputDecoration( - hintText: 'Enter speed (e.g., 1.7)', + hintText: AppLocalizations.of(context)!.enter_speed, errorText: errorMessage.isNotEmpty ? errorMessage : null, ), keyboardType: @@ -61,7 +63,7 @@ void showAddCustomSpeedDialog( ), actions: [ TextButton( - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), onPressed: () { Navigator.of(context).pop(); }, @@ -75,7 +77,7 @@ void showAddCustomSpeedDialog( onSpeedAdded(customSpeed); } : null, - child: const Text('Add'), + child: Text(AppLocalizations.of(context)!.add), ), ], ); diff --git a/lib/views/settings_view/edit_profile_screen_view.dart b/lib/views/settings_view/edit_profile_screen_view.dart index 598d3308..91da3ef3 100644 --- a/lib/views/settings_view/edit_profile_screen_view.dart +++ b/lib/views/settings_view/edit_profile_screen_view.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class EditProfileScreen extends ConsumerStatefulWidget { const EditProfileScreen({super.key}); @@ -41,7 +43,7 @@ class EditProfileScreenState extends ConsumerState { title: Padding( padding: isLandscape ? const EdgeInsets.only(top: 16.0) : EdgeInsets.zero, - child: const Text('Edit Profile'), + child: Text(AppLocalizations.of(context)!.edit_profile), ), ), body: Padding( @@ -53,16 +55,16 @@ class EditProfileScreenState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Preferred Name', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + Text( + AppLocalizations.of(context)!.preferred_name, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), TextField( controller: preferredNameController, maxLength: 50, decoration: InputDecoration( - hintText: 'Enter your preferred name', + hintText: AppLocalizations.of(context)!.enter_preferred_name, border: const OutlineInputBorder(), counterText: '${preferredNameController.text.length}/80', ), @@ -74,11 +76,10 @@ class EditProfileScreenState extends ConsumerState { color: Theme.of(context).colorScheme.scrim.withOpacity(0.50), ), - children: const [ - TextSpan(text: 'You can change this '), + children: [ TextSpan( - text: 'once every three months.', - style: TextStyle( + text: AppLocalizations.of(context)!.name_change_limitation, + style: const TextStyle( fontStyle: FontStyle.italic, fontWeight: FontWeight.bold, ), @@ -93,10 +94,10 @@ class EditProfileScreenState extends ConsumerState { borderRadius: BorderRadius.circular(8.0), ), ), - onPressed: () => _onSaveButtonPressed(), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 12), - child: Text('Save'), + onPressed: () => _onSaveButtonPressed(context), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text(AppLocalizations.of(context)!.save), ), ), const SizedBox(height: 16), @@ -115,12 +116,12 @@ class EditProfileScreenState extends ConsumerState { ); } - void _onSaveButtonPressed() { + void _onSaveButtonPressed(BuildContext context) { if (mounted) { if (preferredNameController.text.trim().isNotEmpty) { _updatePreferredName(preferredNameController.text); } else { - _showErrorDialog('Please enter a preferred name'); + _showErrorDialog(AppLocalizations.of(context)!.enter_preferred_name); } } } @@ -132,11 +133,11 @@ class EditProfileScreenState extends ConsumerState { .updatePreferredName(name); if (success) { setState(() { - infoText = 'Preferred name saved: $name'; + infoText = AppLocalizations.of(context)!.preferred_name_saved(name); isError = false; }); } else { - _showErrorDialog('Preferred name can only be changed every 3 months'); + _showErrorDialog('3 months'); } } catch (e) { _showErrorDialog('An error occurred'); @@ -150,7 +151,7 @@ class EditProfileScreenState extends ConsumerState { return AlertDialog( backgroundColor: Theme.of(context).colorScheme.onPrimary, title: const Text("Error"), - content: Text(errorMessage), + content: Text(errorMessage== '3 months' ? AppLocalizations.of(context)!.name_change_limitation : errorMessage), actions: [ TextButton( child: const Text("OK"), diff --git a/lib/views/settings_view/playback_speed_picker_view.dart b/lib/views/settings_view/playback_speed_picker_view.dart index 8bb39679..b13e0f82 100644 --- a/lib/views/settings_view/playback_speed_picker_view.dart +++ b/lib/views/settings_view/playback_speed_picker_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + void showPlaybackSpeedsPicker( BuildContext context, @@ -43,11 +45,11 @@ void showPlaybackSpeedsPicker( if (selectedSpeeds .any((speed) => !defaultSpeeds.contains(speed))) ...[ const Divider(), - const Padding( - padding: EdgeInsets.all(8.0), + Padding( + padding: const EdgeInsets.all(8.0), child: Text( - 'Custom Playback Speeds', - style: TextStyle(fontWeight: FontWeight.bold), + AppLocalizations.of(context)!.custom_playback_speed, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), for (var speed in selectedSpeeds diff --git a/lib/views/settings_view/playback_speed_settings_view.dart b/lib/views/settings_view/playback_speed_settings_view.dart index 3280e963..5a96a4ac 100644 --- a/lib/views/settings_view/playback_speed_settings_view.dart +++ b/lib/views/settings_view/playback_speed_settings_view.dart @@ -5,6 +5,7 @@ import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/views/settings_view/playback_speed_picker_view.dart'; import 'package:gocast_mobile/views/settings_view/custom_playback_speed_view.dart'; import 'package:gocast_mobile/views/settings_view/authentication_error_card_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class PlaybackSpeedSettings extends ConsumerWidget { const PlaybackSpeedSettings({super.key}); @@ -29,7 +30,7 @@ class PlaybackSpeedSettings extends ConsumerWidget { List playbackSpeeds, ) { return ListTile( - title: const Text('Playback Speeds'), + title: Text(AppLocalizations.of(context)!.playback_speed), trailing: const Icon(Icons.arrow_forward_ios), onTap: () => showPlaybackSpeedsPicker( context, @@ -44,7 +45,7 @@ class PlaybackSpeedSettings extends ConsumerWidget { ListTile _buildCustomPlaybackSpeedsTile(BuildContext context, WidgetRef ref) { return ListTile( - title: const Text('Add Custom Playback Speed'), + title: Text(AppLocalizations.of(context)!.add_custom_playback_speed), onTap: () { showAddCustomSpeedDialog(context, (double customSpeed) { _updateSelectedSpeeds(context, ref, customSpeed, true); diff --git a/lib/views/settings_view/preferred_greeting_view.dart b/lib/views/settings_view/preferred_greeting_view.dart index f752c875..2c67a654 100644 --- a/lib/views/settings_view/preferred_greeting_view.dart +++ b/lib/views/settings_view/preferred_greeting_view.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/views/settings_view/authentication_error_card_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class PreferredGreetingView extends ConsumerWidget { const PreferredGreetingView({super.key}); @@ -19,7 +21,7 @@ class PreferredGreetingView extends ConsumerWidget { 'Default Greeting'; return ListTile( - title: const Text('Preferred Greeting'), + title: Text(AppLocalizations.of(context)!.preferred_greeting), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/views/settings_view/settings_screen_view.dart b/lib/views/settings_view/settings_screen_view.dart index 54bf3e1b..37491cc1 100644 --- a/lib/views/settings_view/settings_screen_view.dart +++ b/lib/views/settings_view/settings_screen_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/providers.dart'; +import 'package:gocast_mobile/utils/UserPreferences.dart'; import 'package:gocast_mobile/views/on_boarding_view/welcome_screen_view.dart'; import 'package:gocast_mobile/views/settings_view/playback_speed_settings_view.dart'; import 'package:gocast_mobile/views/settings_view/preferred_greeting_view.dart'; @@ -8,6 +9,8 @@ import 'package:gocast_mobile/views/settings_view/edit_profile_screen_view.dart' import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/views/settings_view/authentication_error_card_view.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @@ -45,8 +48,8 @@ class _SettingsScreenState extends ConsumerState { children: [ _buildProfileTile(userState), const Divider(), - _buildSectionTitle('Account Settings'), - _buildEditableListTile('Edit profile', () async { + _buildSectionTitle(AppLocalizations.of(context)!.account_settings), + _buildEditableListTile(AppLocalizations.of(context)!.edit_profile, () async { bool isAuthenticated = await showAuthenticationErrorCard(context, ref); if (isAuthenticated && mounted) { @@ -59,7 +62,7 @@ class _SettingsScreenState extends ConsumerState { }), const PreferredGreetingView(), _buildSwitchListTile( - title: 'Push notifications', + title: AppLocalizations.of(context)!.push_notifications, value: settingState.isPushNotificationsEnabled, onChanged: (value) { ref @@ -69,8 +72,9 @@ class _SettingsScreenState extends ConsumerState { ref: ref, ), _buildThemeSelectionTile(context, ref), + _buildLanguageSelectionTile(context, ref), _buildSwitchListTile( - title: 'Download Over Wi-Fi only', + title: AppLocalizations.of(context)!.download_over_wifi_only, value: settingState.isDownloadWithWifiOnly, onChanged: (value) { ref @@ -79,18 +83,18 @@ class _SettingsScreenState extends ConsumerState { }, ref: ref, ), - _buildSectionTitle('Video Playback Speed'), + _buildSectionTitle(AppLocalizations.of(context)!.playback_speed), const PlaybackSpeedSettings(), _buildLogoutTile(context), const Divider(), - _buildSectionTitle('More'), - _buildNavigableListTile('About us', ""), + _buildSectionTitle(AppLocalizations.of(context)!.more), + _buildNavigableListTile(AppLocalizations.of(context)!.about_us, ""), _buildNavigableListTile( - 'Privacy policy', + AppLocalizations.of(context)!.privacy_policy, "https://live.rbg.tum.de/privacy", ), _buildNavigableListTile( - 'Terms and conditions', + AppLocalizations.of(context)!.terms_and_conditions, "https://live.rbg.tum.de/imprint", ), ], @@ -102,7 +106,7 @@ class _SettingsScreenState extends ConsumerState { AppBar _buildAppBar(BuildContext context) { return AppBar( - title: const Text('Settings'), + title: Text(AppLocalizations.of(context)!.settings), leading: IconButton( icon: !_isTablet(context) ? const Icon(Icons.arrow_back_ios) @@ -118,21 +122,53 @@ class _SettingsScreenState extends ConsumerState { String themeModeText; if (settingState.isDarkMode) { - themeModeText = 'Dark Mode'; + themeModeText = AppLocalizations.of(context)!.dark; } else if (settingState.isLightMode) { - themeModeText = 'Light Mode'; + themeModeText = AppLocalizations.of(context)!.light; } else { - themeModeText = 'System Default'; + themeModeText = AppLocalizations.of(context)!.system_default; } - return ListTile( - title: const Text('Choose Theme'), + title: Text(AppLocalizations.of(context)!.choose_theme), subtitle: Text(themeModeText), trailing: const Icon(Icons.arrow_forward_ios), onTap: () => _showThemeSelectionSheet(context, ref), ); } + ListTile _buildLanguageSelectionTile(BuildContext context, WidgetRef ref) { + return ListTile( + title: Text(AppLocalizations.of(context)!.language_selection), + subtitle: Text(UserPreferences.getLanguageName(UserPreferences.getLanguage())), + trailing: const Icon(Icons.arrow_forward_ios), + onTap: () async { + final selectedLanguage = await showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: ['en', 'fr', 'de', 'es'] + .map((String lang) => ListTile( + title: Text(UserPreferences.getLanguageName(lang)), + onTap: () => Navigator.pop(context, lang), + ),) + .toList(), + ), + ); + }, + ); + + if (selectedLanguage != null) { + await UserPreferences.setLanguage(selectedLanguage); + // To rebuild the app + ref.read(settingViewModelProvider.notifier).setLoading(false); + } + }, + ); + } + + void _showThemeSelectionSheet(BuildContext context, WidgetRef ref) { showModalBottomSheet( context: context, @@ -141,7 +177,7 @@ class _SettingsScreenState extends ConsumerState { child: Wrap( children: [ ListTile( - title: const Text('System Default'), + title: Text(AppLocalizations.of(context)!.system_default), onTap: () { ref .read(settingViewModelProvider.notifier) @@ -150,7 +186,7 @@ class _SettingsScreenState extends ConsumerState { }, ), ListTile( - title: const Text('Dark Mode'), + title: Text(AppLocalizations.of(context)!.dark), onTap: () { ref .read(settingViewModelProvider.notifier) @@ -159,7 +195,7 @@ class _SettingsScreenState extends ConsumerState { }, ), ListTile( - title: const Text('Light Mode'), + title: Text(AppLocalizations.of(context)!.light), onTap: () { ref .read(settingViewModelProvider.notifier) @@ -237,7 +273,7 @@ class _SettingsScreenState extends ConsumerState { ListTile _buildLogoutTile(BuildContext context) { return ListTile( title: Text( - 'Log out', + AppLocalizations.of(context)!.logout, style: TextStyle(color: Theme.of(context).colorScheme.error), ), onTap: () => _showLogoutDialog(context), @@ -252,18 +288,18 @@ class _SettingsScreenState extends ConsumerState { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Log Out'), - content: const Text('Would you like to delete all your downloads?'), + title: Text(AppLocalizations.of(context)!.logout), + content: Text(AppLocalizations.of(context)!.logout_message), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), // User chooses not to delete downloads - child: const Text('No'), + child: Text(AppLocalizations.of(context)!.no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), // User chooses to delete downloads - child: const Text('Yes'), + child: Text(AppLocalizations.of(context)!.yes), ), ], ); diff --git a/lib/views/video_view/video_player.dart b/lib/views/video_view/video_player.dart index f94f77f4..a79dd557 100644 --- a/lib/views/video_view/video_player.dart +++ b/lib/views/video_view/video_player.dart @@ -13,6 +13,8 @@ import 'package:gocast_mobile/views/video_view/utils/custom_video_control_bar.da import 'package:gocast_mobile/views/video_view/utils/video_player_handler.dart'; import 'package:gocast_mobile/views/video_view/video_player_controller.dart'; import 'package:logger/logger.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class VideoPlayerPage extends ConsumerStatefulWidget { final Stream stream; @@ -74,12 +76,12 @@ class VideoPlayerPageState extends ConsumerState { await ref .read(courseViewModelProvider.notifier) .getCourseWithID(widget.stream.courseID); + await ref.read(chatViewModelProvider.notifier).fetchChatMessages(widget.stream.id); Course? course = ref .read(courseViewModelProvider) .course; if (course != null) { - if ((course.chatEnabled || course.vodChatEnabled) && - widget.stream.chatEnabled) { + if (course.chatEnabled && course.vodChatEnabled && widget.stream.chatEnabled) { setState(() { _isChatActive = true; _isPollActive = true; @@ -208,7 +210,7 @@ class VideoPlayerPageState extends ConsumerState { } bool _shouldMarkAsWatched(double progress) { - const watchedThreshold = 0.9; // 80% + const watchedThreshold = 0.9; return progress >= watchedThreshold; } @@ -292,7 +294,7 @@ class VideoPlayerPageState extends ConsumerState { String fileName = "stream.mp4"; if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Starting download...')), + SnackBar(content: Text(AppLocalizations.of(context)!.starting_download)), ); // Call the download function from the StreamViewModel diff --git a/pubspec.lock b/pubspec.lock index 8d88fbba..d5e227fd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -262,6 +262,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_riverpod: dependency: "direct main" description: @@ -388,10 +393,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.18.1" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index eed6e5a3..4d6adfaf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: dio_cookie_manager: ^3.1.1 flutter: sdk: flutter - intl: ^0.19.0 + intl: any webview_flutter: ^4.4.2 flutter_inappwebview: ^5.8.0 provider: ^6.1.1 @@ -36,6 +36,8 @@ dependencies: url_launcher: ^6.2.3 path_provider: any jwt_decode: ^0.3.1 + flutter_localizations: + sdk: flutter dev_dependencies: flutter_test: