# Project export: RevitaLang

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Revitalizing endangered languages through the power of storytelling.
- Devpost: https://devpost.com/software/revitalang
- GitHub: https://github.com/julianodportela/revitalang
- Video: https://www.youtube.com/embed/DNNjykhirvk?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Juliano Portela (1 commits)

## Devpost submission (written by the team)

### Overview

Intro Language extinction is a threat for various indigenous languages throughout the world. The combined impact of colonialism, the imposed conformity of a mainstream language, along with the migration of younger generations to urban centers, led traditional languages and dialects face a shrinking generation of native speakers. RevitaLang is a mobile application that works to change this reality. Our team was inspired by the learning method of "language nests" in which elders of the community interact with young children through relevant songs, stories and play -- immersing them in their traditional language. With RevitaLang, we aim to replicate this experience for second or third generation individuals who seek to reconnect with their linguistic heritage. How it works RevitaLang is centered on two main components: 1) Language Learning: we created short sets of activities centered on different day-to-day elements which learners can use to practice their vocabulary and grammar. We believe the authentic source of linguistic knowledge should come directly from the community's native speakers, which is why we have not incorporated AI into the generation of educational content for the app. The courses are meant to be originally created by first-language speakers for second-language learners. 2) Cultural reconnection through cross-generational storytelling: we have included a video section where members of the community can upload videos from the community's elders, who have a higher proficiency over the language, to share traditional stories and songs, so as to bridge distances for learners who may not reside in the community's main location.

### Challenges we ran into

One of the main challenges we ran into involved the ideation process. We had long brainstorming sessions centered how to enhance the language learning experience for the individual while also incorporating a community-based aspect. After discovering about the language nest learning method, we were inspired to create a video upload section which would serve the goals of 1) Promoting the digital inclusion of the community's elders into the documentation and sharing of the language; 2) Enhancing the learner's language immersion experience through the use of storytelling and cross-generational connection.

### Accomplishments we're proud of

We are proud of having incorporated elements which would make the UI more accessible for seniors in traditional communities. Our interface is minimalistic, with large icons and devoid of a clutter of controls and animations. We have also included the main app configurations in indigenous languages so as to make the navigation more intuitive for the elders and enhance the language immersion for learners. Conclusion and Next Steps RevitaLang currently supports four languages native to indigenous communities in Brazil. Beyond TreeHacks, we seek to expand our language database to other indigenous communities around the world, providing language learning support for the growth of native speaker communities of endangered languages. Other technical next steps involve the inclusion of text-to-speech configurations to improve the UI's accessibility and incorporate video querying activities through Multimodal RAG. As a parallel goal, we hope that our app serves as a live documentation of high quality data on endangered languages which could be used to diversify the linguistics context of LLMs, with the respective consent of the communities impacted by RevitaLang.

## README (from the GitHub repository)

# revitalang

Revitalang

## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.


## Detected evidence (automated analysis)

Indexed codebase: 21 recognized source files, 51 KB.
- C (language) — detected in the code
- Dart (language) — detected in the code
- Java (language) — detected in the code
- Swift (language) — detected in the code
- Firebase (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (48 of 48)

```
.gitignore
.metadata
analysis_options.yaml
android/.gitignore
android/app/build.gradle
android/app/src/debug/AndroidManifest.xml
android/app/src/main/AndroidManifest.xml
android/app/src/main/java/com/example/revitalang/MainActivity.java
android/app/src/main/res/drawable-v21/launch_background.xml
android/app/src/main/res/drawable/launch_background.xml
android/app/src/main/res/values-night/styles.xml
android/app/src/main/res/values/styles.xml
android/app/src/profile/AndroidManifest.xml
android/build.gradle
android/gradle.properties
android/gradle/wrapper/gradle-wrapper.properties
android/settings.gradle
ios/.gitignore
ios/Flutter/AppFrameworkInfo.plist
ios/Flutter/Debug.xcconfig
ios/Flutter/Release.xcconfig
ios/Podfile
ios/Podfile.lock
ios/Runner.xcodeproj/project.pbxproj
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
ios/Runner.xcworkspace/contents.xcworkspacedata
ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
ios/Runner/AppDelegate.swift
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
ios/Runner/Base.lproj/LaunchScreen.storyboard
ios/Runner/Base.lproj/Main.storyboard
ios/Runner/Info.plist
ios/Runner/Runner-Bridging-Header.h
ios/RunnerTests/RunnerTests.swift
lib/common/widgets.dart
lib/main.dart
lib/pages/lessonPage.dart
lib/pages/mainPage.dart
lib/pages/splashscreenPage.dart
lib/pages/videoPage.dart
pubspec.lock
pubspec.yaml
README.md
test/widget_test.dart
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### analysis_options.yaml

```yaml
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
  # The lint rules applied to this project can be customized in the
  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
  # included above or to enable additional rules. A list of all available lints
  # and their documentation is published at https://dart.dev/lints.
  #
  # Instead of disabling a lint rule for the entire project in the
  # section below, it can also be suppressed for a single line of code
  # or a specific dart file by using the `// ignore: name_of_lint` and
  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
  # producing the lint.
  rules:
    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

```

### pubspec.yaml

```yaml
name: revitalang
description: RevitaLang
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=3.1.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  webview_flutter: ^4.7.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

  assets:
    - assets/appstore.png

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

```

### test/widget_test.dart

```dart
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:revitalang/main.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(const MyApp());

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

```

### lib/main.dart

```dart
import 'package:flutter/material.dart';
import 'package:revitalang/pages/lessonPage.dart';
import 'package:revitalang/pages/mainPage.dart';
import 'package:revitalang/pages/splashscreenPage.dart';
import 'package:revitalang/pages/videoPage.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        appBarTheme: const AppBarTheme(
          iconTheme: IconThemeData(
            color: Colors.white,
          ),
          color: Colors.red,
          titleTextStyle: TextStyle(
            color: Colors.white,
            fontSize: 20,
            fontWeight: FontWeight.w700
          )
        ),
        bottomAppBarTheme: const BottomAppBarTheme(
          color: Colors.red,
        ),
      ),
      title: 'RevitaLang',
      home: const SplashscreenPage(),
      routes: {
        "/splashscreenPage": (_) => const SplashscreenPage(),
        "/mainPage": (_) => const MainPage(),
        "/lessonPage": (_) => LessonPage(),
        "/videoPage": (_) => const VideoPage(),
      },
    );
  }
}

```

### ios/Runner/Runner-Bridging-Header.h

```c
#import "GeneratedPluginRegistrant.h"

```

### ios/RunnerTests/RunnerTests.swift

```swift
import Flutter
import UIKit
import XCTest

class RunnerTests: XCTestCase {

  func testExample() {
    // If you add code to the Runner application, consider adding tests here.
    // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
  }

}

```

### ios/Runner/AppDelegate.swift

```swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

```

### lib/pages/splashscreenPage.dart

```dart
import 'dart:async';

import 'package:flutter/material.dart';

class SplashscreenPage extends StatefulWidget {
  const SplashscreenPage({super.key});

  @override
  State<SplashscreenPage> createState() => _SplashscreenPageState();
}

class _SplashscreenPageState extends State<SplashscreenPage> {

  @override
  void initState() {
    super.initState();

    Timer(const Duration(seconds: 2), () {
      Navigator.of(context).pushReplacementNamed("/mainPage");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: Image.asset('assets/appstore.png', scale: 2)
      ),
    );
  }
}

```

### lib/pages/videoPage.dart

```dart
import 'dart:math';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter/material.dart';
import 'package:revitalang/common/widgets.dart';

class VideoPage extends StatefulWidget {
  const VideoPage({super.key});

  @override
  State<VideoPage> createState() => _VideoPageState();
}

class _VideoPageState extends State<VideoPage> {

  final WebViewController controller = WebViewController();

  @override
  Widget build(BuildContext context) {

    final args = ModalRoute.of(context)!.settings.arguments as VideoArguments;
    double screenWidth = MediaQuery.of(context).size.width;

    controller.setJavaScriptMode(JavaScriptMode.unrestricted);
    controller.loadRequest(Uri.parse(args.url));

    return Scaffold(
      appBar: AppBar(
        title: const Text("RevitaLang"),
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 15.0),
          child: ListView(
            shrinkWrap: true,
            children: [
              const SizedBox(height: 25),
              Text(
                args.titulo,
                style: const TextStyle(
                  fontSize: 25,
                  fontWeight: FontWeight.w800,
                  color: Colors.black
                )
              ),
              const SizedBox(height: 20,),
              Container(
                height: 200,
                width: screenWidth - 30,
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(15),
                  border: Border.all(
                    color: Colors.grey,
                    width: 2
                  )
                ),
                child: WebViewWidget(
                  controller: controller,
                )
              ),
              const SizedBox(height: 20,),
              const Text(
                "Description:",
                style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.w600,
                    color: Colors.black87
                )
              ),
              const SizedBox(height: 10),
              Text(
                args.descricao,
                style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w400,
                  color: Colors.black54
                )
              ),
              const SizedBox(height: 20,),
              const Text(
                  "Vocabulary:",
                  style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.w600,
                      color: Colors.black87
                  )
              ),
              const SizedBox(height: 10),
              Text(
                  args.vocabulario,
                  style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w400,
                      color: Colors.black54
                  )
              ),
              const SizedBox(height: 20),
              GestureDetector(
                onTap: () {
                  setState(() {
                    Navigator.pushNamed(context, "/lessonPage", arguments: Random().nextInt(4));
                  });
                },
                child: Container(
                  width: screenWidth - 30,
                  height: 50,
                  decoration: BoxDecoration(
                    color: Colors.red,
                    borderRadius: BorderRadius.circular(15),
                  ),
                  child: const Center(
                    child: Text(
                      "GO TO LESSON",
                      style: TextStyle(
                          fontWeight: FontWeight.w800,
                          fontSize: 25,
                          color: Colors.white
                      ),
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 10)
            ],
          ),
        ),
      ),
    );
  }
}

```

### lib/common/widgets.dart

```dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:webview_flutter/webview_flutter.dart';

class VideoArguments {
  String titulo;
  String descricao;
  String vocabulario;
  String url;

  VideoArguments({required this.titulo, required this.descricao, required this.vocabulario, required this.url});
}

class VideoWidget extends StatelessWidget {
  VideoWidget({
    required this.title,
    required this.subtitle,
    required this.descricao,
    required this.vocabulario,
    required this.url,
    super.key});

  final String title;
  final String subtitle;
  final String descricao;
  final String vocabulario;
  final String url;

  @override
  Widget build(BuildContext context) {

    double screenWidth = MediaQuery.of(context).size.width;
    final controller = WebViewController();

    controller.setJavaScriptMode(JavaScriptMode.unrestricted);
    controller.loadRequest(Uri.parse(url));

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 15),
      child: GestureDetector(
        onTap: () {
          Navigator.pushNamed(context, "/videoPage", arguments: VideoArguments(titulo: title, descricao: descricao, vocabulario: vocabulario, url: url));
        },
        child: Container(
          width: screenWidth - 300,
          decoration: BoxDecoration(
            color: Colors.red,
            borderRadius: BorderRadius.circular(15),
            border: Border.all(
              color: Colors.grey,
              width: 1
            )
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(height: 50),
              Container(
                height: 200,
                width: screenWidth - 30,
                child: Stack(
                  alignment: Alignment.center,
                  children: [
                    WebViewWidget(controller: controller),
                    GestureDetector(
                      onTap: () {
                        Navigator.pushNamed(context, "/videoPage", arguments: VideoArguments(titulo: title, descricao: descricao, vocabulario: vocabulario, url: url));
                      },
                      child: Container(
                        height: 200,
                        color: Colors.transparent,
                        width: screenWidth - 30,
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 20),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 15.0),
                child: Text(
                  title,
                  style: const TextStyle(
                    fontSize: 20,
                    color: Colors.white,
                    fontWeight: FontWeight.w800,
                  ),
                ),
              ),
              const SizedBox(height: 10),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 15.0),
                child: Text(
                  subtitle,
                  style: const TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ),
              const SizedBox(height: 20)
            ],
          ),
        ),
      ),
    );
  }
}

class LessonCircleWidget extends StatelessWidget {
  const LessonCircleWidget({required this.title, required this.icon, required this.lessonId, super.key});

  final String title;
  final int lessonId;
  final IconData icon;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.of(context).pushNamed("/lessonPage", arguments: lessonId);
      },
      child: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Stack(
            alignment: Alignment.center,
            children: [
              Container(
                width: 90,
                height: 90,
                decoration: const BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.red,
                ),
              ),
              Container(
                width: 80,
                height: 80,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.red,
                  border: Border.all(
                    color: Colors.white, // The color of the ring
                    width: 5, // The thickness of the ring
                  ),
                ),
                child: Center(
                  child: Icon(
                    icon,
                    size: 50,
                    color: Colors.white,
                  ),
                ),
              ),
            ],
          ),
          Flexible(
            child: Text(
              title,
              style: const TextStyle(
                color: Colors.black87,
                fontSize: 16,
                fontWeight: FontWeight.w500
              ),
            ),
          )
        ],
      ),
    );
  }
}

class LessonWidget extends StatelessWidget {
  const LessonWidget({required this.exercise, super.key});

  final String exercise;

  @override
  Widget build(BuildContext context) {

    double screenWidth = MediaQuery.of(context).size.width;

    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const Text(
          "Answer this exercise:",
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.w700,
            color: Colors.black54
          ),
        ),
        const SizedBox(height: 15,),
        Container(
          width: screenWidth - 50,
          decora
[truncated — 753 more characters]
```

[10 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]