├── Website ├── assets │ ├── environment │ ├── images │ │ ├── logo.png │ │ ├── dark │ │ │ ├── phone.jpg │ │ │ └── launcher.png │ │ ├── phone_view.jpg │ │ ├── terminal.png │ │ ├── icons │ │ │ ├── fallen.png │ │ │ ├── addendum.png │ │ │ ├── altverz.png │ │ │ ├── requiem.png │ │ │ ├── resonance.png │ │ │ └── reimagined.png │ │ ├── light │ │ │ ├── phone.jpg │ │ │ └── launcher.png │ │ └── logos │ │ │ ├── android.png │ │ │ └── windows.png │ └── font │ │ └── GoogleSans-Regular.ttf ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── 404.html │ ├── manifest.json │ ├── index.html │ └── styles.css ├── lib │ ├── bridge │ │ └── proxy.dart │ ├── repository │ │ ├── github_repository.dart │ │ ├── changelog_repository.dart │ │ └── extension_repository.dart │ ├── service │ │ ├── url_helper.dart │ │ ├── download_helper.dart │ │ └── service_locator.dart │ ├── extension │ │ └── context.dart │ ├── api │ │ ├── github_api.dart │ │ ├── changelog_api.dart │ │ └── extension_api.dart │ ├── screen │ │ ├── download │ │ │ ├── download_error.dart │ │ │ ├── thankyou_page.dart │ │ │ └── download_page.dart │ │ ├── changelog │ │ │ ├── changelog_page.dart │ │ │ └── changelog_card.dart │ │ ├── root_page.dart │ │ ├── footer │ │ │ └── footer_widget.dart │ │ ├── extension │ │ │ └── extension_screen.dart │ │ ├── about │ │ │ └── about_page.dart │ │ └── home │ │ │ └── home_page.dart │ ├── main.dart │ ├── model │ │ ├── changelog.dart │ │ ├── extension.dart │ │ ├── assets.dart │ │ ├── github.dart │ │ └── author.dart │ ├── firebase_options.dart │ ├── l10n │ │ ├── app_vi.arb │ │ ├── app_tr.arb │ │ ├── app_uk.arb │ │ ├── app_en.arb │ │ ├── app_es.arb │ │ ├── app_de.arb │ │ └── app_ru.arb │ └── application.dart ├── l10n.yaml ├── firebase.json ├── README.md ├── .gitignore ├── pubspec.yaml ├── .metadata ├── analysis_options.yaml └── pubspec.lock ├── README.md ├── .gitignore └── LICENSE /Website/assets/environment: -------------------------------------------------------------------------------- 1 | WEBSITE=https://api.github.com/repos/Haruma-VN/Sen.Environment/releases/tags -------------------------------------------------------------------------------- /Website/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/web/favicon.png -------------------------------------------------------------------------------- /Website/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/logo.png -------------------------------------------------------------------------------- /Website/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/web/icons/Icon-192.png -------------------------------------------------------------------------------- /Website/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/web/icons/Icon-512.png -------------------------------------------------------------------------------- /Website/assets/images/dark/phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/dark/phone.jpg -------------------------------------------------------------------------------- /Website/assets/images/phone_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/phone_view.jpg -------------------------------------------------------------------------------- /Website/assets/images/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/terminal.png -------------------------------------------------------------------------------- /Website/assets/images/icons/fallen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/icons/fallen.png -------------------------------------------------------------------------------- /Website/assets/images/light/phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/light/phone.jpg -------------------------------------------------------------------------------- /Website/assets/images/dark/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/dark/launcher.png -------------------------------------------------------------------------------- /Website/assets/images/icons/addendum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/icons/addendum.png -------------------------------------------------------------------------------- /Website/assets/images/icons/altverz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/icons/altverz.png -------------------------------------------------------------------------------- /Website/assets/images/icons/requiem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/icons/requiem.png -------------------------------------------------------------------------------- /Website/assets/images/icons/resonance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/icons/resonance.png -------------------------------------------------------------------------------- /Website/assets/images/light/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/light/launcher.png -------------------------------------------------------------------------------- /Website/assets/images/logos/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/logos/android.png -------------------------------------------------------------------------------- /Website/assets/images/logos/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/logos/windows.png -------------------------------------------------------------------------------- /Website/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /Website/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /Website/assets/font/GoogleSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/font/GoogleSans-Regular.ttf -------------------------------------------------------------------------------- /Website/assets/images/icons/reimagined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harumazzz/Sen.Environment/HEAD/Website/assets/images/icons/reimagined.png -------------------------------------------------------------------------------- /Website/lib/bridge/proxy.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop' as js; 2 | 3 | @js.JS('flutterWebRendererReady') 4 | external void flutterWebRendererReady(); 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | - For now and the future, the source code of Sen is fully closed. This repo is closed, which mean 4 | that Sen is no longer remains open sourced. 5 | -------------------------------------------------------------------------------- /Website/l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | output-dir: lib/i18n 4 | output-localization-file: app_localizations.dart 5 | synthetic-package: false 6 | -------------------------------------------------------------------------------- /Website/firebase.json: -------------------------------------------------------------------------------- 1 | {"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"sen-environment","configurations":{"web":"1:55635740445:web:e47ebffb0c844a98b603f4"}}}}}} -------------------------------------------------------------------------------- /Website/lib/repository/github_repository.dart: -------------------------------------------------------------------------------- 1 | import '../api/github_api.dart'; 2 | import '../model/github.dart'; 3 | 4 | class GithubRepository { 5 | const GithubRepository(this._githubApi); 6 | final GithubApi _githubApi; 7 | 8 | Future getRelease() async { 9 | return await _githubApi.getRelease(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Website/lib/repository/changelog_repository.dart: -------------------------------------------------------------------------------- 1 | import '../api/changelog_api.dart'; 2 | import '../model/changelog.dart'; 3 | 4 | class ChangelogRepository { 5 | const ChangelogRepository(this._changelogApi); 6 | final ChangelogApi _changelogApi; 7 | 8 | Future> getChangelog() async { 9 | return await _changelogApi.getChangelog(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Website/lib/service/url_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:url_launcher_web/url_launcher_web.dart'; 2 | 3 | class UrlHelper { 4 | const UrlHelper._(); 5 | 6 | static Future launch({required String link}) async { 7 | final plugin = UrlLauncherPlugin(); 8 | if (!await plugin.launch(link)) { 9 | throw Exception('Could not launch $link'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Website/web/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | Redirecting... 15 | 16 | 17 | -------------------------------------------------------------------------------- /Website/lib/extension/context.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../i18n/app_localizations.dart'; 3 | 4 | extension BuildContextExtension on BuildContext { 5 | AppLocalizations get los => AppLocalizations.of(this)!; 6 | bool get isDarkMode => Theme.of(this).brightness == Brightness.dark; 7 | bool get isDynamicColor => 8 | Theme.of(this).colorScheme.surfaceContainerHighest != 9 | Theme.of(this).colorScheme.surface; 10 | } 11 | -------------------------------------------------------------------------------- /Website/lib/service/download_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:web/web.dart' as web; 2 | 3 | class DownloadHelper { 4 | const DownloadHelper._(); 5 | 6 | static void downloadFile(String url) { 7 | final anchor = web.document.createElement('a') as web.HTMLAnchorElement; 8 | anchor.href = url; 9 | anchor.download = url.split('/').last; 10 | anchor.style.display = 'none'; 11 | web.document.body?.appendChild(anchor); 12 | anchor.click(); 13 | anchor.remove(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Website/lib/api/github_api.dart: -------------------------------------------------------------------------------- 1 | import '../model/github.dart'; 2 | import 'package:dio/dio.dart' as dio; 3 | import '../service/service_locator.dart'; 4 | 5 | class GithubApi { 6 | const GithubApi(); 7 | 8 | Future getRelease() async { 9 | final response = await ServiceLocator.instance.get().get( 10 | '/release', 11 | ); 12 | if (response.statusCode == 200) { 13 | return GitHub.fromJson(response.data); 14 | } else { 15 | throw Exception(response.data.toString()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Website/lib/api/changelog_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import '../model/changelog.dart'; 3 | 4 | class ChangelogApi { 5 | const ChangelogApi(); 6 | 7 | Future> getChangelog() async { 8 | final ref = FirebaseFirestore.instance.collection('changelog'); 9 | final snapshot = await ref.orderBy('date', descending: true).get(); 10 | final List result = []; 11 | result.addAll(snapshot.docs.map((e) => Changelog.fromJson(e.data()))); 12 | return result; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Website/README.md: -------------------------------------------------------------------------------- 1 | # website 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /Website/lib/repository/extension_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | import '../api/extension_api.dart'; 4 | import '../model/extension.dart'; 5 | 6 | class ExtensionRepository { 7 | ExtensionRepository(this._extensionApi); 8 | final ExtensionApi _extensionApi; 9 | 10 | DocumentSnapshot? _previousDocument; 11 | 12 | Future> getExtensions({int limit = 10}) async { 13 | return await _extensionApi.getExtensions( 14 | limit: limit, 15 | previousDocument: _previousDocument, 16 | onFetch: (previousDocument) { 17 | _previousDocument = previousDocument; 18 | }, 19 | ); 20 | } 21 | 22 | bool get canFetch => _previousDocument != null; 23 | } 24 | -------------------------------------------------------------------------------- /Website/lib/screen/download/download_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DownloadError extends StatelessWidget { 4 | const DownloadError({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Center( 9 | child: Column( 10 | mainAxisAlignment: MainAxisAlignment.center, 11 | children: [ 12 | Icon(Icons.warning, color: Colors.red, size: 80), 13 | SizedBox(height: 20), 14 | Text( 15 | 'VPN detected! Maybe try to change the VPN application, or either turn it off if possible, and try reload the page.', 16 | textAlign: TextAlign.center, 17 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), 18 | ), 19 | ], 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Website/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sen: Installation Page", 3 | "short_name": "Sen", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Sen installation page, this is the first page that will be displayed when you install the app.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Website/lib/api/extension_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | import '../model/extension.dart'; 4 | 5 | class ExtensionApi { 6 | const ExtensionApi(); 7 | 8 | Future> getExtensions({ 9 | required int limit, 10 | required DocumentSnapshot? previousDocument, 11 | required Function(DocumentSnapshot? previousDocument) onFetch, 12 | }) async { 13 | final ref = FirebaseFirestore.instance.collection('extension'); 14 | var query = ref.limit(limit); 15 | if (previousDocument != null) { 16 | query = query.startAfterDocument(previousDocument); 17 | } 18 | final snapshot = await query.get(); 19 | if (snapshot.docs.isNotEmpty) { 20 | onFetch(snapshot.docs.last); 21 | } else { 22 | onFetch(null); 23 | } 24 | final result = []; 25 | result.addAll(snapshot.docs.map((e) => Extension.fromJson(e.data()))); 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Website/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | 45 | #environment file 46 | .env 47 | 48 | # l10n generated 49 | /lib/i18n/ 50 | 51 | pubspec.lock -------------------------------------------------------------------------------- /Website/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'application.dart'; 4 | import 'service/service_locator.dart'; 5 | import 'package:firebase_core/firebase_core.dart'; 6 | import 'firebase_options.dart'; 7 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 8 | import 'package:flutter_web_plugins/url_strategy.dart' as url_strategy; 9 | import 'package:flutter/services.dart'; 10 | import 'bridge/proxy.dart'; 11 | 12 | Future main(List arguments) async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | await dotenv.load(fileName: 'assets/environment'); 15 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 16 | url_strategy.usePathUrlStrategy(); 17 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); 18 | ServiceLocator.instance.registerSingleton(); 19 | if (kIsWeb) { 20 | Future.delayed( 21 | const Duration(milliseconds: 600), 22 | () => flutterWebRendererReady(), 23 | ); 24 | } 25 | runApp(const Application()); 26 | } 27 | -------------------------------------------------------------------------------- /Website/lib/model/changelog.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class Changelog extends Equatable { 5 | const Changelog({ 6 | this.date, 7 | this.version, 8 | this.specialThanks, 9 | this.updateChanges, 10 | }); 11 | 12 | factory Changelog.fromJson(Map json) { 13 | return Changelog( 14 | version: json['version'], 15 | date: (json['date'] as Timestamp).toDate(), 16 | specialThanks: 17 | json['special_thanks'] != null 18 | ? (json['special_thanks'] as List) 19 | .map((e) => e.toString()) 20 | .toList() 21 | : null, 22 | updateChanges: 23 | json['update_changes'] != null 24 | ? (json['update_changes'] as List) 25 | .map((e) => e.toString()) 26 | .toList() 27 | : null, 28 | ); 29 | } 30 | final String? version; 31 | final DateTime? date; 32 | final List? specialThanks; 33 | final List? updateChanges; 34 | 35 | @override 36 | List get props => [version, date, specialThanks, updateChanges]; 37 | } 38 | -------------------------------------------------------------------------------- /Website/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: website 2 | description: 'A new Flutter project.' 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ^3.7.0 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | cupertino_icons: ^1.0.8 12 | get_it: ^8.0.3 13 | material_symbols_icons: ^4.2801.0 14 | hyperlink: ^0.0.5 15 | url_launcher_web: ^2.3.3 16 | firebase_core: ^3.8.1 17 | cloud_firestore: ^5.5.1 18 | web: ^1.1.0 19 | dio: ^5.8.0+1 20 | flutter_dotenv: ^5.2.1 21 | equatable: ^2.0.7 22 | flutter_localizations: 23 | sdk: flutter 24 | go_router: ^14.8.1 25 | flutter_web_plugins: 26 | sdk: flutter 27 | url_launcher: ^6.3.1 28 | shimmer: ^3.0.0 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | flutter_lints: ^5.0.0 34 | flutter: 35 | uses-material-design: true 36 | generate: true 37 | assets: 38 | - assets/ 39 | - assets/images/ 40 | - assets/images/dark/ 41 | - assets/images/light/ 42 | - assets/images/icons/ 43 | - assets/images/logos/ 44 | - assets/environment 45 | fonts: 46 | - family: GoogleSans 47 | fonts: 48 | - asset: assets/font/GoogleSans-Regular.ttf 49 | -------------------------------------------------------------------------------- /Website/lib/model/extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class Extension extends Equatable { 4 | const Extension({ 5 | required this.extensionId, 6 | required this.extensionTitle, 7 | required this.extensionDescription, 8 | required this.downloadLink, 9 | required this.extensionAuthor, 10 | }); 11 | factory Extension.fromJson(Map map) { 12 | return Extension( 13 | extensionId: map['extension_id'] as int, 14 | extensionTitle: map['extension_title'] as String, 15 | extensionDescription: map['extension_description'] as String, 16 | downloadLink: map['download_link'] as String, 17 | extensionAuthor: map['extension_author'] as String, 18 | ); 19 | } 20 | 21 | final int extensionId; 22 | final String extensionTitle; 23 | final String extensionDescription; 24 | final String extensionAuthor; 25 | final String downloadLink; 26 | 27 | @override 28 | List get props => [ 29 | extensionId, 30 | extensionTitle, 31 | extensionDescription, 32 | extensionAuthor, 33 | downloadLink, 34 | ]; 35 | 36 | Map toJson() { 37 | return { 38 | 'extensionId': extensionId, 39 | 'extensionTitle': extensionTitle, 40 | 'extensionDescription': extensionDescription, 41 | 'extensionAuthor': extensionAuthor, 42 | 'downloadLink': downloadLink, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Website/lib/service/service_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 2 | import 'package:get_it/get_it.dart'; 3 | import '../api/changelog_api.dart'; 4 | import '../api/extension_api.dart'; 5 | import '../api/github_api.dart'; 6 | import '../repository/changelog_repository.dart'; 7 | import '../repository/extension_repository.dart'; 8 | import '../repository/github_repository.dart'; 9 | import 'package:dio/dio.dart' as dio; 10 | 11 | class ServiceLocator { 12 | const ServiceLocator._(); 13 | static final GetIt _getIt = GetIt.asNewInstance(); 14 | 15 | static const ServiceLocator instance = ServiceLocator._(); 16 | 17 | void registerIfNot(T object) { 18 | if (!_getIt.isRegistered()) { 19 | _getIt.registerSingleton(object); 20 | } 21 | } 22 | 23 | void registerSingleton() { 24 | registerIfNot( 25 | dio.Dio( 26 | dio.BaseOptions( 27 | baseUrl: dotenv.get('WEBSITE'), 28 | connectTimeout: const Duration(seconds: 60), 29 | receiveTimeout: const Duration(seconds: 60), 30 | ), 31 | ), 32 | ); 33 | registerIfNot(const GithubRepository(GithubApi())); 34 | registerIfNot( 35 | const ChangelogRepository(ChangelogApi()), 36 | ); 37 | registerIfNot( 38 | ExtensionRepository(const ExtensionApi()), 39 | ); 40 | } 41 | 42 | T get() { 43 | assert(_getIt.isRegistered(), '$T is not registered'); 44 | return _getIt.get(); 45 | } 46 | 47 | Future reset() async => await _getIt.reset(); 48 | } 49 | -------------------------------------------------------------------------------- /Website/lib/model/assets.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'author.dart'; 3 | 4 | class Assets extends Equatable { 5 | const Assets({ 6 | this.url, 7 | this.id, 8 | this.nodeId, 9 | this.name, 10 | this.label, 11 | this.uploader, 12 | this.contentType, 13 | this.state, 14 | this.size, 15 | this.downloadCount, 16 | this.createdAt, 17 | this.updatedAt, 18 | this.browserDownloadUrl, 19 | }); 20 | 21 | factory Assets.fromJson(Map json) { 22 | return Assets( 23 | url: json['url'], 24 | id: json['id'], 25 | nodeId: json['node_id'], 26 | name: json['name'], 27 | label: json['label'], 28 | uploader: 29 | json['uploader'] != null ? Author.fromJson(json['uploader']) : null, 30 | contentType: json['content_type'], 31 | state: json['state'], 32 | size: json['size'], 33 | downloadCount: json['download_count'], 34 | createdAt: json['created_at'], 35 | updatedAt: json['updated_at'], 36 | browserDownloadUrl: json['browser_download_url'], 37 | ); 38 | } 39 | final String? url; 40 | final int? id; 41 | final String? nodeId; 42 | final String? name; 43 | final String? label; 44 | final Author? uploader; 45 | final String? contentType; 46 | final String? state; 47 | final int? size; 48 | final int? downloadCount; 49 | final String? createdAt; 50 | final String? updatedAt; 51 | final String? browserDownloadUrl; 52 | 53 | @override 54 | List get props => [ 55 | url, 56 | id, 57 | nodeId, 58 | name, 59 | label, 60 | uploader, 61 | contentType, 62 | state, 63 | size, 64 | downloadCount, 65 | createdAt, 66 | updatedAt, 67 | browserDownloadUrl, 68 | ]; 69 | } 70 | -------------------------------------------------------------------------------- /Website/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 17 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 18 | - platform: android 19 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 20 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 21 | - platform: ios 22 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 23 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 24 | - platform: linux 25 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 26 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 27 | - platform: macos 28 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 29 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 30 | - platform: web 31 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 32 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 33 | - platform: windows 34 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 35 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /Website/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | - always_put_control_body_on_new_line 6 | - avoid_double_and_int_checks 7 | - avoid_empty_else 8 | - avoid_function_literals_in_foreach_calls 9 | - avoid_init_to_null 10 | - avoid_null_checks_in_equality_operators 11 | - avoid_positional_boolean_parameters 12 | - avoid_print 13 | - avoid_redundant_argument_values 14 | - avoid_returning_null_for_future 15 | - avoid_setters_without_getters 16 | - avoid_slow_async_io 17 | - avoid_unnecessary_containers 18 | - avoid_unused_constructor_parameters 19 | - cancel_subscriptions 20 | - close_sinks 21 | - constant_identifier_names 22 | - control_flow_in_finally 23 | - diagnostic_describe_all_properties 24 | - empty_catches 25 | - empty_constructor_bodies 26 | - exhaustive_cases 27 | - flutter_style_todos 28 | - join_return_with_assignment 29 | - library_private_types_in_public_api 30 | - literal_only_boolean_expressions 31 | - no_duplicate_case_values 32 | - null_closures 33 | - package_names 34 | - parameter_assignments 35 | - prefer_collection_literals 36 | - prefer_const_constructors 37 | - prefer_const_declarations 38 | - prefer_const_literals_to_create_immutables 39 | - prefer_final_fields 40 | - prefer_final_in_for_each 41 | - prefer_foreach 42 | - prefer_function_declarations_over_variables 43 | - prefer_initializing_formals 44 | - prefer_interpolation_to_compose_strings 45 | - prefer_mixin 46 | - prefer_relative_imports 47 | - prefer_single_quotes 48 | - require_trailing_commas 49 | - secure_pubspec_urls 50 | - sort_constructors_first 51 | - sort_unnamed_constructors_first 52 | - tighten_type_of_initializing_formals 53 | - type_annotate_public_apis 54 | - unawaited_futures 55 | - unnecessary_null_checks 56 | - unnecessary_parenthesis 57 | - unnecessary_this 58 | - use_function_type_syntax_for_parameters 59 | - use_key_in_widget_constructors 60 | - use_late_for_private_fields_and_variables 61 | - use_setters_to_change_properties 62 | - valid_regexps 63 | -------------------------------------------------------------------------------- /Website/lib/model/github.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'assets.dart'; 3 | import 'author.dart'; 4 | 5 | class GitHub extends Equatable { 6 | const GitHub({ 7 | this.url, 8 | this.assetsUrl, 9 | this.uploadUrl, 10 | this.htmlUrl, 11 | this.id, 12 | this.author, 13 | this.nodeId, 14 | this.tagName, 15 | this.targetCommitish, 16 | this.name, 17 | this.draft, 18 | this.prerelease, 19 | this.createdAt, 20 | this.publishedAt, 21 | this.assets, 22 | this.tarballUrl, 23 | this.zipballUrl, 24 | this.body, 25 | }); 26 | 27 | factory GitHub.fromJson(Map json) { 28 | return GitHub( 29 | url: json['url'], 30 | assetsUrl: json['assets_url'], 31 | uploadUrl: json['upload_url'], 32 | htmlUrl: json['html_url'], 33 | id: json['id'], 34 | author: json['author'] != null ? Author.fromJson(json['author']) : null, 35 | nodeId: json['node_id'], 36 | tagName: json['tag_name'], 37 | targetCommitish: json['target_commitish'], 38 | name: json['name'], 39 | draft: json['draft'], 40 | prerelease: json['prerelease'], 41 | createdAt: json['created_at'], 42 | publishedAt: json['published_at'], 43 | assets: 44 | json['assets'] != null 45 | ? (json['assets'] as List).map((v) => Assets.fromJson(v)).toList() 46 | : null, 47 | tarballUrl: json['tarball_url'], 48 | zipballUrl: json['zipball_url'], 49 | body: json['body'], 50 | ); 51 | } 52 | final String? url; 53 | final String? assetsUrl; 54 | final String? uploadUrl; 55 | final String? htmlUrl; 56 | final int? id; 57 | final Author? author; 58 | final String? nodeId; 59 | final String? tagName; 60 | final String? targetCommitish; 61 | final String? name; 62 | final bool? draft; 63 | final bool? prerelease; 64 | final String? createdAt; 65 | final String? publishedAt; 66 | final List? assets; 67 | final String? tarballUrl; 68 | final String? zipballUrl; 69 | final String? body; 70 | 71 | @override 72 | List get props => [ 73 | url, 74 | assetsUrl, 75 | uploadUrl, 76 | htmlUrl, 77 | id, 78 | author, 79 | nodeId, 80 | tagName, 81 | targetCommitish, 82 | name, 83 | draft, 84 | prerelease, 85 | createdAt, 86 | publishedAt, 87 | assets, 88 | tarballUrl, 89 | zipballUrl, 90 | body, 91 | ]; 92 | } 93 | -------------------------------------------------------------------------------- /Website/lib/model/author.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class Author extends Equatable { 4 | const Author({ 5 | this.login, 6 | this.id, 7 | this.nodeId, 8 | this.avatarUrl, 9 | this.gravatarId, 10 | this.url, 11 | this.htmlUrl, 12 | this.followersUrl, 13 | this.followingUrl, 14 | this.gistsUrl, 15 | this.starredUrl, 16 | this.subscriptionsUrl, 17 | this.organizationsUrl, 18 | this.reposUrl, 19 | this.eventsUrl, 20 | this.receivedEventsUrl, 21 | this.type, 22 | this.userViewType, 23 | this.siteAdmin, 24 | }); 25 | 26 | factory Author.fromJson(Map json) { 27 | return Author( 28 | login: json['login'], 29 | id: json['id'], 30 | nodeId: json['node_id'], 31 | avatarUrl: json['avatar_url'], 32 | gravatarId: json['gravatar_id'], 33 | url: json['url'], 34 | htmlUrl: json['html_url'], 35 | followersUrl: json['followers_url'], 36 | followingUrl: json['following_url'], 37 | gistsUrl: json['gists_url'], 38 | starredUrl: json['starred_url'], 39 | subscriptionsUrl: json['subscriptions_url'], 40 | organizationsUrl: json['organizations_url'], 41 | reposUrl: json['repos_url'], 42 | eventsUrl: json['events_url'], 43 | receivedEventsUrl: json['received_events_url'], 44 | type: json['type'], 45 | userViewType: json['user_view_type'], 46 | siteAdmin: json['site_admin'], 47 | ); 48 | } 49 | final String? login; 50 | final int? id; 51 | final String? nodeId; 52 | final String? avatarUrl; 53 | final String? gravatarId; 54 | final String? url; 55 | final String? htmlUrl; 56 | final String? followersUrl; 57 | final String? followingUrl; 58 | final String? gistsUrl; 59 | final String? starredUrl; 60 | final String? subscriptionsUrl; 61 | final String? organizationsUrl; 62 | final String? reposUrl; 63 | final String? eventsUrl; 64 | final String? receivedEventsUrl; 65 | final String? type; 66 | final String? userViewType; 67 | final bool? siteAdmin; 68 | 69 | @override 70 | List get props => [ 71 | login, 72 | id, 73 | nodeId, 74 | avatarUrl, 75 | gravatarId, 76 | url, 77 | htmlUrl, 78 | followersUrl, 79 | followingUrl, 80 | gistsUrl, 81 | starredUrl, 82 | subscriptionsUrl, 83 | organizationsUrl, 84 | reposUrl, 85 | eventsUrl, 86 | receivedEventsUrl, 87 | type, 88 | userViewType, 89 | siteAdmin, 90 | ]; 91 | } 92 | -------------------------------------------------------------------------------- /Website/lib/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: type=lint 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | import 'package:flutter/foundation.dart' 5 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 6 | 7 | /// Default [FirebaseOptions] for use with your Firebase apps. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// import 'firebase_options.dart'; 12 | /// // ... 13 | /// await Firebase.initializeApp( 14 | /// options: DefaultFirebaseOptions.currentPlatform, 15 | /// ); 16 | /// ``` 17 | class DefaultFirebaseOptions { 18 | static FirebaseOptions get currentPlatform { 19 | if (kIsWeb) { 20 | return web; 21 | } 22 | switch (defaultTargetPlatform) { 23 | case TargetPlatform.android: 24 | throw UnsupportedError( 25 | 'DefaultFirebaseOptions have not been configured for android - ' 26 | 'you can reconfigure this by running the FlutterFire CLI again.', 27 | ); 28 | case TargetPlatform.iOS: 29 | throw UnsupportedError( 30 | 'DefaultFirebaseOptions have not been configured for ios - ' 31 | 'you can reconfigure this by running the FlutterFire CLI again.', 32 | ); 33 | case TargetPlatform.macOS: 34 | throw UnsupportedError( 35 | 'DefaultFirebaseOptions have not been configured for macos - ' 36 | 'you can reconfigure this by running the FlutterFire CLI again.', 37 | ); 38 | case TargetPlatform.windows: 39 | throw UnsupportedError( 40 | 'DefaultFirebaseOptions have not been configured for windows - ' 41 | 'you can reconfigure this by running the FlutterFire CLI again.', 42 | ); 43 | case TargetPlatform.linux: 44 | throw UnsupportedError( 45 | 'DefaultFirebaseOptions have not been configured for linux - ' 46 | 'you can reconfigure this by running the FlutterFire CLI again.', 47 | ); 48 | default: 49 | throw UnsupportedError( 50 | 'DefaultFirebaseOptions are not supported for this platform.', 51 | ); 52 | } 53 | } 54 | 55 | static const FirebaseOptions web = FirebaseOptions( 56 | apiKey: 'AIzaSyD50cLSRC9RnPHZJfWCX9eLa4r57PHzE1k', 57 | appId: '1:55635740445:web:e47ebffb0c844a98b603f4', 58 | messagingSenderId: '55635740445', 59 | projectId: 'sen-environment', 60 | authDomain: 'sen-environment.firebaseapp.com', 61 | storageBucket: 'sen-environment.firebasestorage.app', 62 | measurementId: 'G-WNGWKPWD4W', 63 | ); 64 | 65 | } -------------------------------------------------------------------------------- /Website/lib/screen/changelog/changelog_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '../../model/changelog.dart'; 4 | import '../../repository/changelog_repository.dart'; 5 | import 'changelog_card.dart'; 6 | import '../footer/footer_widget.dart'; 7 | import '../../service/service_locator.dart'; 8 | 9 | class ChangelogPage extends StatefulWidget { 10 | const ChangelogPage({super.key, required this.onNavigate}); 11 | 12 | final void Function(int index) onNavigate; 13 | 14 | @override 15 | State createState() => _ChangelogPageState(); 16 | 17 | @override 18 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 19 | super.debugFillProperties(properties); 20 | properties.add( 21 | ObjectFlagProperty.has( 22 | 'onNavigate', 23 | onNavigate, 24 | ), 25 | ); 26 | } 27 | } 28 | 29 | class _ChangelogPageState extends State 30 | with AutomaticKeepAliveClientMixin { 31 | late Future> _future; 32 | 33 | @override 34 | void initState() { 35 | _future = ServiceLocator.instance.get().getChangelog(); 36 | super.initState(); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | void didChangeDependencies() { 46 | super.didChangeDependencies(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | super.build(context); 52 | return SingleChildScrollView( 53 | child: Column( 54 | children: [ 55 | Padding( 56 | padding: const EdgeInsets.all(16.0), 57 | child: FutureBuilder( 58 | future: _future, 59 | builder: (context, snapshot) { 60 | if (snapshot.connectionState == ConnectionState.waiting) { 61 | return const Center( 62 | child: CircularProgressIndicator.adaptive(), 63 | ); 64 | } else if (snapshot.hasError) { 65 | return Center(child: Text(snapshot.error.toString())); 66 | } else if (snapshot.hasData) { 67 | final data = snapshot.data!; 68 | return Column( 69 | children: [...data.map((e) => ChangelogCard(changelog: e))], 70 | ); 71 | } else { 72 | return const SizedBox.shrink(); 73 | } 74 | }, 75 | ), 76 | ), 77 | FooterWidget(onNavigate: widget.onNavigate), 78 | ], 79 | ), 80 | ); 81 | } 82 | 83 | @override 84 | bool get wantKeepAlive => true; 85 | } 86 | -------------------------------------------------------------------------------- /Website/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Sen: Installation page 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 |
59 |
60 | 61 |

Haruma is cooking, please wait

63 |
64 | 65 | 66 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Website/lib/l10n/app_vi.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "Giới thiệu", 3 | "build_your_mod_faster": "Tạo ra mod PvZ2 nhanh hơn!", 4 | "changelog": "Thay đổi", 5 | "cmake_description": "Hệ thống xây dựng đa nền tảng cho C++. Nó là một công cụ được sử dụng rộng rãi trong cộng đồng C++.", 6 | "community_forum": "Cộng đồng", 7 | "community_support": "Cộng đồng hỗ trợ", 8 | "contact": "Liên hệ", 9 | "date": "Ngày", 10 | "discord_server": "Máy chủ Discord", 11 | "documentation": "Tài liệu", 12 | "download": "Tải xuống", 13 | "download_again": "Tải lại", 14 | "download_for_android": "Tải xuống cho Android", 15 | "download_for_windows": "Tải xuống cho Windows", 16 | "download_now": "Tải xuống công cụ", 17 | "download_page": "Trang tải xuống", 18 | "download_start_automatically": "Yêu cầu tải xuống được thực hiện ngay lập tức. Nếu không thể tải xuống, vui lòng thử sử dụng liên kết bên dưới.", 19 | "faq": "Hỏi đáp", 20 | "file_type": "Định dạng tệp", 21 | "flutter_description": "Framework triển giao diện người dùng đẹp do Google phát triển. Chủ yếu được sử dụng trong ứng dụng GUI và cả trang web này.", 22 | "having_download_problem": "Bạn gặp rắc rối? Hãy thử đường dẫn trực tiếp xem sao.", 23 | "having_issue": "Nếu bạn gặp rắc rối với Sen, hãy thử tải xuống", 24 | "home": "Trang chủ", 25 | "improve_mod_production": "Cải thiện hiệu suất làm việc lên đến 20% bằng Sen", 26 | "kernel_description": "Backend, lõi của chương trình.", 27 | "make_your_own_mod_with_sen": "Làm mod PvZ2 của riêng bạn bằng Sen", 28 | "modding_description": "Giao diện người dùng hỗ trợ đa dạng tasks.", 29 | "modules": "Các Mô-đun", 30 | "open_source_project": "Dự án mã nguồn mở", 31 | "open_source_project_description": "Dự án này nằm dưới điều khoản GPL-3.0, cho phép sử dụng và sửa đổi hoàn toàn miễn phí miễn là tuân theo điều khoản.", 32 | "platform_support": "Nền tảng hỗ trợ", 33 | "quality_assurance": "Đề cao chất lượng", 34 | "quality_ensurance_description": "Sen cung cấp một lượng lớn công cụ giúp bạn làm việc hiệu quả hơn", 35 | "quick_links": "Liên kết nhanh", 36 | "resources": "Tài nguyên", 37 | "script_description": "Tập lệnh kiểm soát luồng hoạt động của công cụ.", 38 | "sen_agreement": "Bằng việc tải và sử dụng Sen, bạn chấp nhận các điều khoản được cấp phép và yêu cầu về quyền riêng tư.", 39 | "sen_big_community_support": "Sen có một cộng đồng hỗ trợ lớn giúp đỡ bạn khi cần thiết", 40 | "sen_first_description": "Sen là tất cả những gì bạn cần! Công cụ làm tất cả mọi việc, được tin cậy bởi cộng đồng modders quốc tế.", 41 | "sen_promotional_description": "Sen: Environment là một dự án hỗ trợ đa thiết bị, đa nền tảng và có khả năng thực hiện tác vụ cực kỳ phức tạp.", 42 | "sen_promotional_title": "Một dự án mã nguồn mở tạo ra bởi Haruma.", 43 | "sen_supported_mod": "Sen hiện đang hỗ trợ những mods này và nhiều hơn nữa", 44 | "sen_title": "Miễn phí và mở mã nguồn. Nhanh và hiệu quả, không yêu cầu nặng nề về phần cứng.", 45 | "shell_description": "Frontend, cung cấp giao diện tương tác.", 46 | "special_thanks": "Gửi lời cảm ơn", 47 | "support": "Hỗ trợ", 48 | "system": "Hệ thống", 49 | "thank_you_for_download": "Cảm ơn vì bạn đã tin tưởng tải xuống Sen: Environment!", 50 | "total_sen_users": "Đang có {count} người dùng đã tải xuống phiên bản hiện tại của Sen.", 51 | "update_changes": "Cập nhật thay đổi", 52 | "version": "Phiên bản", 53 | "why_sen": "Tại sao lại là Sen?", 54 | "technology": "Công nghệ" 55 | } -------------------------------------------------------------------------------- /Website/lib/l10n/app_tr.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "Hakkında", 3 | "build_your_mod_faster": "Pvz 2 modunuzu daha hızlı oluşturun.", 4 | "changelog": "Değişiklik Günlüğü", 5 | "cmake_description": "C++ için platform derleme sistemi. C++ topluluğunda herkesin kullandığı bir araçtır.", 6 | "community_forum": "Topluluk Forumu", 7 | "community_support": "Topluluk Desteği", 8 | "contact": "Bizimle iletişime geçin", 9 | "date": "Tarih", 10 | "discord_server": "Discord Sunucu", 11 | "documentation": "Dokümantasyon", 12 | "download": "İndir", 13 | "download_again": "Tekrar indir", 14 | "download_for_android": "Android'e indir", 15 | "download_for_windows": "Windowsa indir", 16 | "download_now": "Şimdi indir", 17 | "download_page": "Sayfayı İndir", 18 | "download_start_automatically": "Eğer indirme baslamassa, aşağıdaki bağlantıyı kullanın.", 19 | "faq": "Soru Cevap", 20 | "file_type": "Dosya türü", 21 | "flutter_description": "Google tarafından geliştirilen güzel UI geliştirme çerçevesi. Çoğunlukla GUI uygulamasında ve ayrıca bu web sitesinde kullanılır.", 22 | "having_download_problem": "Bir sorun mu yaşıyorsunuz? Aşağıdaki bağlantıyı kullanın.", 23 | "having_issue": "Sen ile ilgili herhangi bir problem yaşıyorsanız, yüklemeyi deneyin", 24 | "home": "Ev", 25 | "improve_mod_production": "Sen ile modlama hızınızı %20 oranında artırın", 26 | "kernel_description": "Backend, programın çekirdeğidir.", 27 | "make_your_own_mod_with_sen": "Sen ile kendi PvZ2 modunuzu yapın", 28 | "modding_description": "Yardımcı görevleri destekleyen Grafik Kullanıcı Arayüzü.", 29 | "modules": "Modül", 30 | "open_source_project": "Açık Kaynak Projesi", 31 | "open_source_project_description": "Bu proje GPL-3.0 Lisansı altında lisanslanmıştır ve şartlara uyulduğu sürece ücretsiz kullanım ve değişiklik yapılmasına izin verilmektedir.", 32 | "platform_support": "Platform Desteği", 33 | "quality_assurance": "Kalite Güvencesi", 34 | "quality_ensurance_description": "Sen, iş akışınızı kolaylaştırmak için çok sayıda araç sunabilir.", 35 | "quick_links": "Hızlı Bağlantılar", 36 | "resources": "Kaynaklar", 37 | "script_description": "Aracın komut dosyası, kontrol akışları.", 38 | "sen_agreement": "Sen'i indirip kullanırsanız, lisans koşullarını ve gizlilik bildirimini kabul edersiniz.", 39 | "sen_big_community_support": "Sen, oyunu kolayca değiştirmenize yardımcı olacak büyük bir toplulukla birlikte gelir.", 40 | "sen_first_description": "Sen ihtiyacınız olan şeydir! Hepsi bir arada araç, çoğu modcular tarafından önerilir.", 41 | "sen_promotional_description": "Sen: Environment, çoklu cihazlara hizmet vermek üzere progranmış, platformlar arası yetenekler ve sağlam bir modüler mimari sunan bir projedir.", 42 | "sen_promotional_title": "Haruma tarafından tasarlanan açık kaynaklı bir projedir.", 43 | "sen_supported_mod": "Sen şu anda bu modları ve çok daha fazlasını destekleyebilir.", 44 | "sen_title": "Ücretsiz ve açık kaynaklı. Hızlı ve etkili, düşük cihaz gereksinimi.", 45 | "shell_description": "Önyüz, komut satırı arayüzü sağlar.", 46 | "special_thanks": "Özel Teşekkürler", 47 | "support": "Destek", 48 | "system": "Sistem", 49 | "thank_you_for_download": "Sen Tool'u indirdiğiniz için teşekkürler!", 50 | "total_sen_users": "Bu Sen sürümünü indiren {count} kullanıcı var.", 51 | "update_changes": "Değişiklikleri güncelle", 52 | "version": "Version", 53 | "why_sen": "Neden Sen?", 54 | "technology": "Teknoloji" 55 | } -------------------------------------------------------------------------------- /Website/lib/l10n/app_uk.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "About", 3 | "build_your_mod_faster": "Build your PvZ2 mod faster!", 4 | "changelog": "Changelog", 5 | "cmake_description": "Cross-platform build system for C++. It is a wide used tool in the C++ community.", 6 | "community_forum": "Форум", 7 | "community_support": "Тех Підтримка", 8 | "contact": "Зв'язатися з нами", 9 | "date": "Дата", 10 | "discord_server": "Діскорд Сервер", 11 | "documentation": "Документація", 12 | "download": "Встановити", 13 | "download_again": "Встановити ще раз", 14 | "download_for_android": "Встановити для Android", 15 | "download_for_windows": "Встановити для Windows", 16 | "download_now": "Встановити зараз", 17 | "download_page": "Сторінка встановлення", 18 | "download_start_automatically": "Процесс завантаження повинен разпочатися автоматично. Якщо ні, то викоростайте посилання нижче.", 19 | "faq": "Питання та Відповіді", 20 | "file_type": "Тип файлу", 21 | "flutter_description": "Гарний Интерфейс зробленний Google. Викоростованне у GUI Версії та на цій сторінці.", 22 | "having_download_problem": "Виникли проблеми? Скористуйтеся прямим посиланнаям на завантаження.", 23 | "having_issue": "Якщо у вас є якісь проблеми з Сеном, спробйте його завантажити", 24 | "home": "Головна Сторінка", 25 | "improve_mod_production": "Збільшить ефективність розробки вашого моду на 20% з Сеном", 26 | "kernel_description": "Бєкенж, це ядро программи.", 27 | "make_your_own_mod_with_sen": "Зроби свою модифікацію на PvZ2 з Сеном", 28 | "modding_description": "Графіческий користувацький інтерфейс який підтримує утілітни завдання.", 29 | "modules": "Модулі", 30 | "open_source_project": "Проект з відкритим кодом", 31 | "open_source_project_description": "Цей проект знаходится під ліцензією GPL-3.0, який довзоляє використовувати та модифікувати до тих пір поки умови не порушенні.", 32 | "platform_support": "Підтримка Платформ", 33 | "quality_assurance": "Гарантія якості", 34 | "quality_ensurance_description": "Сен пропонує тонну інструментів для оптимізації робочого процесу", 35 | "quick_links": "Швидкі переадресації", 36 | "resources": "Ресурси", 37 | "script_description": "Скрипт котролює потоки утиліти.", 38 | "sen_agreement": "Коли ви встановлюєте сен, ві погоджуєтесь з умовами ліцензії та положенням про конфіденційність.", 39 | "sen_big_community_support": "Сен йде у пакунку з великим комюніті яке допоможе вам модифікувати гру легко", 40 | "sen_first_description": "Сен це саме те що вам треба! Все в одному утиліті, рекомендується багатьма модерами.", 41 | "sen_promotional_description": "Сен: Середовище, проєкт який зробленний щоб підтримувати багато платформ, та предлагає кросс-платофрм підтримку та надійну модульну архітектуру.", 42 | "sen_promotional_title": "Проєкт з відкритим кодом зроблений Harum'ою.", 43 | "sen_supported_mod": "Сен наразі підтримує ці моди та багато інших", 44 | "sen_title": "Безкоштовний та з відкритим кодом. Швидкий так ефективний, низький вимоги до пристрою.", 45 | "shell_description": "Фронтєнд, надає інтерфейс коммандної строки.", 46 | "special_thanks": "Особлива подяка", 47 | "support": "Підтримка", 48 | "system": "Система", 49 | "thank_you_for_download": "Дякуємо за інсталювання Сен: Середовище", 50 | "total_sen_users": "Зараз {count} юзерів які встановили, та вже користуються цією версиєю Сену.", 51 | "update_changes": "Основні Зміни", 52 | "version": "Версія", 53 | "why_sen": "Чому сен?", 54 | "technology": "Технологія" 55 | } -------------------------------------------------------------------------------- /Website/lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "About", 3 | "build_your_mod_faster": "Build your PvZ2 mod faster!", 4 | "changelog": "Changelog", 5 | "cmake_description": "Cross-platform build system for C++. It is a wide used tool in the C++ community.", 6 | "community_forum": "Community Forum", 7 | "community_support": "Community Support", 8 | "contact": "Contact us", 9 | "date": "Date", 10 | "discord_server": "Discord Server", 11 | "documentation": "Documentation", 12 | "download": "Download", 13 | "download_again": "Download Again", 14 | "download_for_android": "Download for Android", 15 | "download_for_windows": "Download for Windows", 16 | "download_now": "Download now", 17 | "download_page": "Download Page", 18 | "download_start_automatically": "Your download should start automatically. If not, use the link below.", 19 | "faq": "FAQ", 20 | "file_type": "File type", 21 | "flutter_description": "Beautiful UI development framework developed by Google. It is mainly used in the GUI app and also this website.", 22 | "having_download_problem": "Having trouble? Use the direct download link.", 23 | "having_issue": "If you are having any issues with Sen, try installing", 24 | "home": "Home", 25 | "improve_mod_production": "Improve your mod production speed by 20% with Sen", 26 | "kernel_description": "Backend, the core of the program.", 27 | "make_your_own_mod_with_sen": "Make your own PvZ2 mod with Sen", 28 | "modding_description": "Graphic User Interface that supports utility tasks.", 29 | "modules": "Modules", 30 | "open_source_project": "Open Source Project", 31 | "open_source_project_description": "This project is licensed under the GPL-3.0 License, allowing free use and modification as long as the terms are followed.", 32 | "platform_support": "Platform Support", 33 | "quality_assurance": "Quality Assurance", 34 | "quality_ensurance_description": "Sen offers tons of tools to streamline your workflow", 35 | "quick_links": "Quick Links", 36 | "resources": "Resources", 37 | "script_description": "Script control flows of the tool.", 38 | "sen_agreement": "By downloading and using Sen, you agree to the license terms and privacy statement.", 39 | "sen_big_community_support": "Sen comes with a big community to help you modify the game easily", 40 | "sen_first_description": "Sen is what you need! The all-in-one tool, is recommended by most modders.", 41 | "sen_promotional_description": "Sen: Environment is a project designed to serve multiple devices, offering cross-platform capabilities and a robust modular architecture.", 42 | "sen_promotional_title": "An open-source project created by Haruma.", 43 | "sen_supported_mod": "Sen is currently supporting these mods and much more", 44 | "sen_title": "Free and open-source. Fast and efficient, low device requirement.", 45 | "shell_description": "Frontend, provides a command-line interface.", 46 | "special_thanks": "Special Thanks To", 47 | "support": "Support", 48 | "system": "System", 49 | "thank_you_for_download": "Thank you for downloading Sen: Environment!", 50 | "total_sen_users": "There are {count} users who have downloaded this Sen version.", 51 | "update_changes": "Update changes", 52 | "version": "Version", 53 | "why_sen": "Why Sen?", 54 | "technology": "Technology", 55 | "network_restriction": "Network Restriction", 56 | "extension": "Extension", 57 | "extension_title": "Sen's Extension", 58 | "extension_description": "Here are some extensions that recommended by Haruma to help you use Sen simpler.", 59 | "author": "Author", 60 | "download_success": "Download success!" 61 | } -------------------------------------------------------------------------------- /Website/lib/l10n/app_es.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "Acerca de", 3 | "build_your_mod_faster": "¡Haz tu propio mod de PvZ 2 más rápido!", 4 | "changelog": "Lista de cambios", 5 | "cmake_description": "Sistema de compilación multiplataforma para C++. Es una herramienta muy usada en la comunidad de C++.", 6 | "community_forum": "Foro de la comunidad", 7 | "community_support": "Ayuda de la comunidad", 8 | "contact": "Contáctanos", 9 | "date": "Fecha", 10 | "discord_server": "Servidor de Discord", 11 | "documentation": "Documentación", 12 | "download": "Descargar", 13 | "download_again": "Descargar de nuevo", 14 | "download_for_android": "Descargar para Android", 15 | "download_for_windows": "Descargar para Windows", 16 | "download_now": "Descargar ahora", 17 | "download_page": "Descargar página", 18 | "download_start_automatically": "Tu descarga debería empezar automáticamente. Si no es el caso, use el enlace debajo.", 19 | "faq": "Preguntas frecuentes", 20 | "file_type": "Tipo de archivo", 21 | "flutter_description": "Gran sistema de desarrollo para interfaces de usuario (IU) desarrollado por Google. Se utiliza principalmente en la aplicación GUI y también en este sitio web.", 22 | "having_download_problem": "¿Algún problema? Use el enlace de descarga directa.", 23 | "having_issue": "Si tienes algún problema con Sen, intenta instalarlo.", 24 | "home": "Inicio", 25 | "improve_mod_production": "Haz que la producción de tu mod sea un 20% más rápida con Sen.", 26 | "kernel_description": "Backend, el núcleo del programa.", 27 | "make_your_own_mod_with_sen": "Crea tu propio mod de PvZ 2 con Sen", 28 | "modding_description": "Interfaz gráfica de usuario que ayuda con tareas útiles.", 29 | "modules": "Módulos", 30 | "open_source_project": "Proyecto de código abierto", 31 | "open_source_project_description": "Este proyecto está bajo la licencia GPL-3.0, permitiendo libre modificación y uso mientras los términos sean seguidos.", 32 | "platform_support": "Soporte de plataforma", 33 | "quality_assurance": "Seguro de calidad", 34 | "quality_ensurance_description": "Sen ofrece varias herramientas para mejorar tu flujo de trabajo", 35 | "quick_links": "Enlaces rápidos", 36 | "resources": "Recursos", 37 | "script_description": "Flujos de control de scripts de la herramienta.", 38 | "sen_agreement": "Al descargar y usar Sen, acepta los términos de licencia y la declaración de privacidad.", 39 | "sen_big_community_support": "Sen tiene una gran comunidad para ayudarte a modificar el juego fácilmente", 40 | "sen_first_description": "¡Sen es lo que necesitas! La herramienta que lo tiene todo, es recomendada por la mayoría de los modders.", 41 | "sen_promotional_description": "Sen: Environment es un proyecto designado para funcionar en múltiples dispositivos, logrando ser multiplataforma y dar una arquitectura modular robusta.", 42 | "sen_promotional_title": "Un proyecto de código abierto creado por Haruma.", 43 | "sen_supported_mod": "Actualmente, Sen da soporte a estos mods y muchos más", 44 | "sen_title": "Gratis y de código aberto. Rápido y eficiente, hecho para dispositivos de gama baja.", 45 | "shell_description": "Frontend, da una interfaz de comandos.", 46 | "special_thanks": "Agradecimientos especiales a", 47 | "support": "Soporte", 48 | "system": "Sistema", 49 | "thank_you_for_download": "¡Gracias por descargar Sen: Environment!", 50 | "total_sen_users": "Hay {count} usuario que han descargado esta versión de Sen.", 51 | "update_changes": "Cambios en la actualización", 52 | "version": "Versión", 53 | "why_sen": "¿Por qué usar Sen?", 54 | "technology": "Tecnología" 55 | } -------------------------------------------------------------------------------- /Website/lib/l10n/app_de.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "Über:", 3 | "build_your_mod_faster": "Erstelle PvZ2 mods schneller als je zuvor!", 4 | "changelog": "Änderungsprotokoll", 5 | "cmake_description": "Multi-platform bau System für C++. Es ist ein häufig genutzes Werkzeug in der C++ Community.", 6 | "community_forum": "Community Forum", 7 | "community_support": "Community Unterstützung", 8 | "contact": "Kontaktiere uns", 9 | "date": "Datum", 10 | "discord_server": "Discord Server", 11 | "documentation": "Dokumentation", 12 | "download": "Herunterladen", 13 | "download_again": "Erneut Herunterladen", 14 | "download_for_android": "Für Android Herunterladen", 15 | "download_for_windows": "Für Windows Herunterladen", 16 | "download_now": "Jetzt Herunterladen!", 17 | "download_page": "Installations Seite", 18 | "download_start_automatically": "Das Herunterladen sollte automatisch beginnen. Falls dies nicht so ist, Nutzte den Link unten.", 19 | "faq": "FAQ", 20 | "file_type": "Dateityp", 21 | "flutter_description": "Wunderschönes UI-Entwicklungsframework von Google. Es wird hauptsächlich in der GUI-App und auch auf dieser Website verwendet.", 22 | "having_download_problem": "Probleme beim Herunterladen? Dann nutze den direkten Link.", 23 | "having_issue": "Falls du Probleme mit Sen hast, versuche das hier zu Herunterladen", 24 | "home": "Startseite", 25 | "improve_mod_production": "Verbessere deine Modifkations Geschwindigkeit um 20% mit Sen", 26 | "kernel_description": "Backend, der Kern des Programms.", 27 | "make_your_own_mod_with_sen": "Erstelle Deine eigene PvZ2 mod mit Sen", 28 | "modding_description": "Grafisches Nutzer Menü Welches Utility-Aufgaben unterstützt.", 29 | "modules": "Module", 30 | "open_source_project": "open source Projekt", 31 | "open_source_project_description": "Dieses Projekt ist lizensiert unter der GPL-3.0 Lizenz, und erlaubt die freie Nutzung und Änderung, solange die Bedingungen eingehalten werden.", 32 | "platform_support": "Platform Unterstützung", 33 | "quality_assurance": "Qualitäts Garantie", 34 | "quality_ensurance_description": "Sen hat jede Menge Werkzeuge um die Arbeit zu erleichtern.", 35 | "quick_links": "Schnelle Links", 36 | "resources": "Resourcen", 37 | "script_description": "Script Die Steuerung des Werkzeugs.", 38 | "sen_agreement": "Durch das Herunterladen und nutzen von Sen stimmtst du den Lizenzbedingungen und Datenschutzerklärungen zu.", 39 | "sen_big_community_support": "Sen kommt mit einer großen Community welche dir beim modifizieren von Pvz2 helfen kann.", 40 | "sen_first_description": "Sen ist das was du brauchst! Dieses alles in einem Werkzeug wird von den meisten Pvz2 Modifizierenden empfohlen!", 41 | "sen_promotional_description": "Sen: Environment ein Werkzeug erstellt für mehrere Geräte und bietet plattformübergreifende Funktionen sowie eine robuste modulare Architektur.", 42 | "sen_promotional_title": "Ein open-source projekt erstellt von Haruma.", 43 | "sen_supported_mod": "Sen unterstützt aktuell diese Mods und noch viele weitere", 44 | "sen_title": "Free and open-source. Schnell und effizient, sowie niedrige Gerätsanforderungen.", 45 | "shell_description": "Frontend, bietet eine Befehlszeilenschnittstelle an.", 46 | "special_thanks": "Spezielles Dankeschön geht an", 47 | "support": "Unterstützung", 48 | "system": "System", 49 | "thank_you_for_download": "Danke für das Herunterladen von Sen: Environment!", 50 | "total_sen_users": "Es gibt {count} Nutzer welche diese Version von Sen bereits vor dir Heruntergeladen haben.", 51 | "update_changes": "Update Änderungen", 52 | "version": "Version", 53 | "why_sen": "Warum Sen?", 54 | "technology": "Technologie" 55 | } -------------------------------------------------------------------------------- /Website/lib/l10n/app_ru.arb: -------------------------------------------------------------------------------- 1 | { 2 | "about": "О Sen", 3 | "build_your_mod_faster": "Создавай моды на PvZ2 быстрее чем когда-либо!", 4 | "changelog": "Список изменений", 5 | "cmake_description": "Кроcсплатформенная утилита для автоматической сборки программы из исходного кода для C++. Данное ПО довольно распространено среди C++ разработчиков.", 6 | "community_forum": "Форум сообщества", 7 | "community_support": "Поддержка от сообщества", 8 | "contact": "Свяжитесь с нами", 9 | "date": "Дата", 10 | "discord_server": "Discord сервер", 11 | "documentation": "Документация", 12 | "download": "Скачать", 13 | "download_again": "Скачать заново", 14 | "download_for_android": "Скачать для Android", 15 | "download_for_windows": "Скачать для Windows", 16 | "download_now": "Скачать сейчас", 17 | "download_page": "Страница загрузки", 18 | "download_start_automatically": "Загрузка должна начаться автоматически через несколько секунд. В противном случае воспользуйтесь ссылкой ниже.", 19 | "faq": "ЧАВО", 20 | "file_type": "Тип файла", 21 | "flutter_description": "Фреймворк для создания UI, разработанный Google. Он использовался для создания GUI версии приложения и этого сайта.", 22 | "having_download_problem": "Столкнулись с проблемой? Используйте данную прямую ссылку на скачивание.", 23 | "having_issue": "Если у вас наблюдаются проблемы с работой Sen, установите", 24 | "home": "Главная", 25 | "improve_mod_production": "Увеличьте скорость создания модов на 20% с помощью Sen", 26 | "kernel_description": "Бэкенд, ядро данного ПО.", 27 | "make_your_own_mod_with_sen": "Создайте свой мод для PvZ2 с помощью Sen", 28 | "modding_description": "Графический пользовательский интерфейс, поддерживающий утилитарные задачи.", 29 | "modules": "Модули", 30 | "open_source_project": "Проект с открытым исходным кодом", 31 | "open_source_project_description": "Этот проект распространяется под лицензией GPL-3.0, позволяя свободное использование и модификацию при соблюдении условий лицензии.", 32 | "platform_support": "Минимальные системные требования", 33 | "quality_assurance": "Обеспечение качества", 34 | "quality_ensurance_description": "Sen предлагает множество инструментов для оптимизации вашего рабочего процесса.", 35 | "quick_links": "Быстрые ссылки", 36 | "resources": "Ресурсы", 37 | "script_description": "Скрипты контролируют поведение приложения.", 38 | "sen_agreement": "Скачивая и используя Sen, вы соглашаетесь с условиями лицензии и политикой конфиденциальности.", 39 | "sen_big_community_support": "У Sen есть большое сообщество, которое поможет вам легко модифицировать игру", 40 | "sen_first_description": "Sen — это всё, что вам нужно! Этот универсальный инструмент рекомендуется большинством мододелов.", 41 | "sen_promotional_description": "Sen: Environment — это проект, разработанный для работы на различных устройствах, предлагающий кроссплатформенные возможности и надежную модульную архитектуру.", 42 | "sen_promotional_title": "Проект с открытым исходным кодом, созданный Haruma.", 43 | "sen_supported_mod": "Sen поддерживает данные моды и множество других тоже", 44 | "sen_title": "Бесплатный и с открытым исходным кодом. Быстрый и эффективный, с низкими системными требованиями.", 45 | "shell_description": "Фронтенд, предоставляет интерфейс командной строки.", 46 | "special_thanks": "Особые благодарности", 47 | "support": "Поддержка", 48 | "system": "Архитектура", 49 | "thank_you_for_download": "Благодарим за скачивание Sen: Environment!", 50 | "total_sen_users": "{count} пользователей уже скачали Sen.", 51 | "update_changes": "Изменения", 52 | "version": "Версия", 53 | "why_sen": "Почему стоит выбрать Sen?", 54 | "technology": "Использовались для разработки" 55 | } -------------------------------------------------------------------------------- /Website/lib/screen/root_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | import '../extension/context.dart'; 5 | 6 | class RootPage extends StatelessWidget { 7 | const RootPage({ 8 | super.key, 9 | required this.child, 10 | required this.selectedIndex, 11 | required this.onNavigate, 12 | }); 13 | final Widget child; 14 | 15 | final int selectedIndex; 16 | 17 | final void Function(int index) onNavigate; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar(title: Text(context.los.download_page)), 23 | body: child, 24 | drawer: AppDrawer(selectedIndex: selectedIndex, onNavigate: onNavigate), 25 | ); 26 | } 27 | 28 | @override 29 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 30 | super.debugFillProperties(properties); 31 | properties.add(IntProperty('selectedIndex', selectedIndex)); 32 | properties.add( 33 | ObjectFlagProperty.has( 34 | 'onNavigate', 35 | onNavigate, 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | class AppDrawer extends StatelessWidget { 42 | const AppDrawer({ 43 | super.key, 44 | required this.onNavigate, 45 | required this.selectedIndex, 46 | }); 47 | 48 | final void Function(int index) onNavigate; 49 | final int selectedIndex; 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Drawer( 54 | child: ListView( 55 | padding: EdgeInsets.zero, 56 | children: [ 57 | _buildDrawerHeader(context), 58 | _buildMenuItem(context.los.home, 0, context, '/'), 59 | _buildMenuItem(context.los.download, 1, context, '/download'), 60 | _buildMenuItem(context.los.changelog, 2, context, '/changelog'), 61 | _buildMenuItem(context.los.about, 3, context, '/about'), 62 | _buildMenuItem(context.los.extension, 4, context, '/extension'), 63 | ], 64 | ), 65 | ); 66 | } 67 | 68 | Widget _buildDrawerHeader(BuildContext context) { 69 | final isDarkMode = Theme.of(context).brightness == Brightness.dark; 70 | return DrawerHeader( 71 | decoration: BoxDecoration( 72 | gradient: LinearGradient( 73 | colors: 74 | isDarkMode 75 | ? const [Color(0xFF393E46), Color(0xFF222831)] 76 | : const [Color(0xFFFFE4E1), Color(0xFFFFB6C1)], 77 | begin: Alignment.topLeft, 78 | end: Alignment.bottomRight, 79 | ), 80 | ), 81 | child: Column( 82 | mainAxisAlignment: MainAxisAlignment.center, 83 | spacing: 10.0, 84 | children: [ 85 | Image.asset('assets/images/logo.png', height: 80, width: 80), 86 | const Text('Sen: Environment', style: TextStyle(fontSize: 22.0)), 87 | ], 88 | ), 89 | ); 90 | } 91 | 92 | Widget _buildMenuItem( 93 | String title, 94 | int index, 95 | BuildContext context, 96 | String route, 97 | ) { 98 | return ListTile( 99 | title: Text(title), 100 | selected: selectedIndex == index, 101 | onTap: () { 102 | onNavigate(index); 103 | context.go(route); 104 | Navigator.of(context).pop(); 105 | }, 106 | ); 107 | } 108 | 109 | @override 110 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 111 | super.debugFillProperties(properties); 112 | properties.add( 113 | ObjectFlagProperty.has( 114 | 'onNavigate', 115 | onNavigate, 116 | ), 117 | ); 118 | properties.add(IntProperty('selectedIndex', selectedIndex)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Website/web/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | overflow: hidden; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | height: 100vh; 8 | width: 100vw; 9 | background-color: #262626; 10 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 11 | } 12 | 13 | #splash-screen { 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | width: 100%; 19 | height: 100%; 20 | position: relative; 21 | } 22 | 23 | #splash-logo { 24 | width: 120px; 25 | height: auto; 26 | filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.3)); 27 | position: relative; 28 | z-index: 1; 29 | } 30 | 31 | #loading-text { 32 | font-size: 1.1rem; 33 | font-weight: 500; 34 | color: #e0e0e0; 35 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); 36 | display: flex; 37 | align-items: baseline; 38 | position: absolute; 39 | bottom: 50px; 40 | left: 50%; 41 | transform: translateX(-50%); 42 | overflow: hidden; 43 | flex-direction: row; 44 | } 45 | 46 | #loading-text::before { 47 | content: ""; 48 | position: absolute; 49 | top: 0; 50 | left: -100%; 51 | width: 50%; 52 | height: 100%; 53 | background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent); 54 | animation: shimmer 1.8s infinite linear; 55 | mask-image: linear-gradient(90deg, transparent, black, transparent); 56 | } 57 | 58 | @keyframes shimmer { 59 | 100% { 60 | left: 200%; 61 | } 62 | } 63 | 64 | #ellipsis { 65 | display: flex; 66 | gap: 4px; 67 | margin-left: 0.5em; 68 | align-items: baseline; 69 | } 70 | 71 | #ellipsis span { 72 | width: 5px; 73 | height: 5px; 74 | background-color: #e0e0e0; 75 | border-radius: 50%; 76 | animation: listening 1.3s infinite ease-in-out; 77 | } 78 | 79 | #ellipsis span:nth-child(2) { 80 | animation-delay: 0.2s; 81 | } 82 | 83 | #ellipsis span:nth-child(3) { 84 | animation-delay: 0.4s; 85 | } 86 | 87 | @keyframes listening { 88 | 89 | 0%, 90 | 100% { 91 | transform: scale(1); 92 | opacity: 0.5; 93 | } 94 | 95 | 50% { 96 | transform: scale(1.4); 97 | opacity: 1; 98 | } 99 | } 100 | 101 | #pumping-circle { 102 | position: absolute; 103 | width: 200px; 104 | height: 200px; 105 | border-radius: 50%; 106 | background: radial-gradient(circle, #546e7a, #37474f); 107 | box-shadow: 0 0 20px rgba(84, 110, 122, 0.5); 108 | animation: pulse 2.2s infinite ease-in-out; 109 | display: flex; 110 | align-items: center; 111 | justify-content: center; 112 | z-index: 0; 113 | } 114 | 115 | @keyframes pulse { 116 | 117 | 0%, 118 | 100% { 119 | transform: scale(1); 120 | } 121 | 122 | 50% { 123 | transform: scale(1.1); 124 | } 125 | } 126 | 127 | .wave { 128 | position: absolute; 129 | top: 50%; 130 | left: 50%; 131 | width: 200px; 132 | height: 200px; 133 | border-radius: 50%; 134 | transform: translate(-50%, -50%); 135 | background: transparent; 136 | box-shadow: 0 0 25px rgba(84, 110, 122, 0.4); 137 | opacity: 0; 138 | animation: waveAnimation 1.5s linear infinite; 139 | } 140 | 141 | .wave:nth-child(2) { 142 | animation-delay: 0.15s; 143 | } 144 | 145 | .wave:nth-child(3) { 146 | animation-delay: 0.3s; 147 | } 148 | 149 | .wave:nth-child(4) { 150 | animation-delay: 0.45s; 151 | } 152 | 153 | .wave:nth-child(5) { 154 | animation-delay: 0.6s; 155 | } 156 | 157 | .wave:nth-child(6) { 158 | animation-delay: 0.75s; 159 | } 160 | 161 | .wave:nth-child(7) { 162 | animation-delay: 0.9s; 163 | } 164 | 165 | .wave:nth-child(8) { 166 | animation-delay: 1.05s; 167 | } 168 | 169 | .wave:nth-child(9) { 170 | animation-delay: 1.2s; 171 | } 172 | 173 | .wave:nth-child(10) { 174 | animation-delay: 1.35s; 175 | } 176 | 177 | .wave:nth-child(11) { 178 | animation-delay: 1.5s; 179 | } 180 | 181 | @keyframes waveAnimation { 182 | 0% { 183 | opacity: 0; 184 | transform: translate(-50%, -50%) scale(1); 185 | } 186 | 187 | 50% { 188 | opacity: 0.7; 189 | transform: translate(-50%, -50%) scale(1.3); 190 | } 191 | 192 | 100% { 193 | opacity: 0; 194 | transform: translate(-50%, -50%) scale(1.6); 195 | } 196 | } 197 | 198 | svg { 199 | position: absolute; 200 | width: 0; 201 | height: 0; 202 | } 203 | 204 | 205 | @media (max-width: 600px) { 206 | #loading-text { 207 | display: none; 208 | } 209 | 210 | #splash-logo { 211 | width: 100px; 212 | } 213 | 214 | #pumping-circle { 215 | width: 160px; 216 | height: 160px; 217 | } 218 | 219 | .wave { 220 | width: 160px; 221 | height: 160px; 222 | } 223 | } -------------------------------------------------------------------------------- /Website/lib/screen/changelog/changelog_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '../../extension/context.dart'; 4 | import '../../model/changelog.dart'; 5 | 6 | class ChangelogCard extends StatelessWidget { 7 | const ChangelogCard({super.key, required this.changelog}); 8 | final Changelog changelog; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final theme = Theme.of(context); 13 | final textColor = 14 | theme.brightness == Brightness.dark ? Colors.white : Colors.black87; 15 | final screenWidth = MediaQuery.sizeOf(context).width; 16 | final isSmallScreen = screenWidth < 600; 17 | 18 | return Card( 19 | margin: EdgeInsets.all(isSmallScreen ? 8.0 : 16.0), 20 | elevation: 4, 21 | shadowColor: Colors.black26, 22 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), 23 | child: Padding( 24 | padding: EdgeInsets.all(isSmallScreen ? 12.0 : 20.0), 25 | child: Column( 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | children: [ 28 | _buildHeader(context, isSmallScreen, textColor), 29 | const SizedBox(height: 12), 30 | const Divider(), 31 | _buildSectionTitle( 32 | '${context.los.update_changes}:', 33 | textColor, 34 | isSmallScreen, 35 | ), 36 | _buildListSection( 37 | changelog.updateChanges, 38 | textColor, 39 | isSmallScreen, 40 | ), 41 | const Divider(), 42 | _buildSectionTitle( 43 | '${context.los.special_thanks}:', 44 | textColor, 45 | isSmallScreen, 46 | ), 47 | _buildListSection( 48 | changelog.specialThanks, 49 | textColor, 50 | isSmallScreen, 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | 58 | Widget _buildHeader( 59 | BuildContext context, 60 | bool isSmallScreen, 61 | Color textColor, 62 | ) { 63 | return Row( 64 | spacing: 12.0, 65 | children: [ 66 | Container( 67 | width: isSmallScreen ? 30 : 40, 68 | height: isSmallScreen ? 30 : 40, 69 | decoration: const BoxDecoration( 70 | image: DecorationImage( 71 | image: AssetImage('assets/images/logo.png'), 72 | fit: BoxFit.cover, 73 | ), 74 | ), 75 | ), 76 | Column( 77 | crossAxisAlignment: CrossAxisAlignment.start, 78 | spacing: 4.0, 79 | children: [ 80 | Text( 81 | '${context.los.version}: ${changelog.version}', 82 | style: TextStyle( 83 | color: textColor, 84 | fontSize: isSmallScreen ? 16 : 18, 85 | fontWeight: FontWeight.bold, 86 | ), 87 | ), 88 | Text( 89 | '${context.los.date}: ${changelog.date?.toLocal().toString().split(' ')[0]}', 90 | style: TextStyle( 91 | color: Colors.grey.shade600, 92 | fontSize: isSmallScreen ? 12 : 14, 93 | ), 94 | ), 95 | ], 96 | ), 97 | ], 98 | ); 99 | } 100 | 101 | Widget _buildSectionTitle(String title, Color textColor, bool isSmallScreen) { 102 | return Padding( 103 | padding: const EdgeInsets.only(bottom: 8.0), 104 | child: Text( 105 | title, 106 | style: TextStyle( 107 | color: textColor, 108 | fontSize: isSmallScreen ? 14 : 18, 109 | fontWeight: FontWeight.bold, 110 | ), 111 | ), 112 | ); 113 | } 114 | 115 | Widget _buildListSection( 116 | List? items, 117 | Color textColor, 118 | bool isSmallScreen, 119 | ) { 120 | if (items == null || items.isEmpty) { 121 | return const SizedBox.shrink(); 122 | } 123 | 124 | return Column( 125 | crossAxisAlignment: CrossAxisAlignment.start, 126 | children: 127 | items.map((item) { 128 | return Padding( 129 | padding: const EdgeInsets.symmetric(vertical: 4.0), 130 | child: Text( 131 | '- $item', 132 | style: TextStyle( 133 | color: textColor, 134 | fontSize: isSmallScreen ? 14 : 16, 135 | ), 136 | ), 137 | ); 138 | }).toList(), 139 | ); 140 | } 141 | 142 | @override 143 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 144 | super.debugFillProperties(properties); 145 | properties.add(DiagnosticsProperty('changelog', changelog)); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Website/lib/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'i18n/app_localizations.dart'; 4 | import 'screen/about/about_page.dart'; 5 | import 'screen/changelog/changelog_page.dart'; 6 | import 'screen/download/download_page.dart'; 7 | import 'screen/download/thankyou_page.dart'; 8 | import 'screen/extension/extension_screen.dart'; 9 | import 'screen/home/home_page.dart'; 10 | import 'screen/root_page.dart'; 11 | import 'dart:ui' as ui; 12 | 13 | class Application extends StatelessWidget { 14 | const Application({super.key}); 15 | 16 | Locale _getInitialLocale() { 17 | final String browserLocale = 18 | ui.PlatformDispatcher.instance.locale.toLanguageTag().split('-').first; 19 | for (final locale in AppLocalizations.supportedLocales) { 20 | if (locale.languageCode == browserLocale) { 21 | return locale; 22 | } 23 | } 24 | return const Locale('en'); 25 | } 26 | 27 | static int _getSelectedIndex(String location) { 28 | switch (location) { 29 | case '/': 30 | return 0; 31 | case '/download': 32 | return 1; 33 | case '/changelog': 34 | return 2; 35 | case '/about': 36 | return 3; 37 | case '/extension': 38 | return 4; 39 | default: 40 | return 0; 41 | } 42 | } 43 | 44 | static final GoRouter _router = GoRouter( 45 | initialLocation: '/', 46 | redirect: (context, state) { 47 | if (state.matchedLocation.isEmpty) { 48 | return '/'; 49 | } 50 | return null; 51 | }, 52 | routes: [ 53 | ShellRoute( 54 | builder: (context, state, child) { 55 | return RootPage( 56 | selectedIndex: _getSelectedIndex(state.matchedLocation), 57 | onNavigate: (index) { 58 | GoRouter.of(context).go(_getPath(index)); 59 | }, 60 | child: child, 61 | ); 62 | }, 63 | routes: [ 64 | GoRoute( 65 | path: '/', 66 | builder: 67 | (context, state) => HomePage( 68 | onNavigate: (index) { 69 | GoRouter.of(context).go(_getPath(index)); 70 | }, 71 | ), 72 | ), 73 | GoRoute( 74 | path: '/download', 75 | builder: 76 | (context, state) => DownloadPage( 77 | onNavigate: (index) { 78 | GoRouter.of(context).go(_getPath(index)); 79 | }, 80 | ), 81 | ), 82 | GoRoute( 83 | path: '/download/success', 84 | builder: (context, state) { 85 | final extra = state.extra as Map; 86 | return ThankYouPage( 87 | isWindows: extra['isWindows'] as bool, 88 | link: extra['link'] as String, 89 | onNavigate: extra['onNavigate'] as void Function(int), 90 | ); 91 | }, 92 | ), 93 | GoRoute( 94 | path: '/changelog', 95 | builder: 96 | (context, state) => ChangelogPage( 97 | onNavigate: (index) { 98 | GoRouter.of(context).go(_getPath(index)); 99 | }, 100 | ), 101 | ), 102 | GoRoute( 103 | path: '/about', 104 | builder: 105 | (context, state) => AboutPage( 106 | onNavigate: (index) { 107 | GoRouter.of(context).go(_getPath(index)); 108 | }, 109 | ), 110 | ), 111 | GoRoute( 112 | path: '/extension', 113 | builder: 114 | (context, state) => ExtensionsPage( 115 | onNavigate: (index) { 116 | GoRouter.of(context).go(_getPath(index)); 117 | }, 118 | ), 119 | ), 120 | ], 121 | ), 122 | ], 123 | ); 124 | 125 | static String _getPath(int index) { 126 | switch (index) { 127 | case 0: 128 | return '/'; 129 | case 1: 130 | return '/download'; 131 | case 2: 132 | return '/changelog'; 133 | case 3: 134 | return '/about'; 135 | case 4: 136 | return '/extension'; 137 | default: 138 | return '/'; 139 | } 140 | } 141 | 142 | @override 143 | Widget build(BuildContext context) { 144 | return Builder( 145 | builder: (context) { 146 | return MaterialApp.router( 147 | title: 'Sen: Download page', 148 | theme: ThemeData( 149 | pageTransitionsTheme: PageTransitionsTheme( 150 | builders: 151 | Map.fromIterable( 152 | TargetPlatform.values, 153 | value: (_) => const FadeForwardsPageTransitionsBuilder(), 154 | ), 155 | ), 156 | fontFamily: 'GoogleSans', 157 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 158 | useMaterial3: true, 159 | ), 160 | debugShowCheckedModeBanner: false, 161 | darkTheme: ThemeData( 162 | pageTransitionsTheme: PageTransitionsTheme( 163 | builders: 164 | Map.fromIterable( 165 | TargetPlatform.values, 166 | value: (_) => const FadeForwardsPageTransitionsBuilder(), 167 | ), 168 | ), 169 | fontFamily: 'GoogleSans', 170 | brightness: Brightness.dark, 171 | colorScheme: ColorScheme.fromSeed( 172 | seedColor: Colors.deepPurpleAccent, 173 | brightness: Brightness.dark, 174 | ).copyWith(surface: Colors.black, onSurface: Colors.white), 175 | scaffoldBackgroundColor: Colors.black, 176 | useMaterial3: true, 177 | dialogTheme: DialogTheme(backgroundColor: Colors.grey.shade900), 178 | ), 179 | localizationsDelegates: AppLocalizations.localizationsDelegates, 180 | supportedLocales: AppLocalizations.supportedLocales, 181 | locale: _getInitialLocale(), 182 | routerConfig: _router, 183 | ); 184 | }, 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Website/lib/screen/download/thankyou_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:hyperlink/hyperlink.dart'; 4 | import '../../extension/context.dart'; 5 | import '../footer/footer_widget.dart'; 6 | import '../../service/download_helper.dart'; 7 | 8 | class ThankYouPage extends StatelessWidget { 9 | const ThankYouPage({ 10 | super.key, 11 | required this.isWindows, 12 | required this.link, 13 | required this.onNavigate, 14 | }); 15 | 16 | final bool isWindows; 17 | final String link; 18 | final void Function(int index) onNavigate; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final isDarkTheme = Theme.of(context).brightness == Brightness.dark; 23 | return Center( 24 | child: SingleChildScrollView( 25 | child: LayoutBuilder( 26 | builder: (context, constraints) { 27 | final isSmallScreen = constraints.maxWidth < 600; 28 | return Column( 29 | spacing: 32.0, 30 | children: [ 31 | Padding( 32 | padding: EdgeInsets.all(isSmallScreen ? 12.0 : 24.0), 33 | child: Column( 34 | spacing: 32.0, 35 | children: [ 36 | _buildTitle(context), 37 | _buildDownloadCard(context, isDarkTheme, isSmallScreen), 38 | ], 39 | ), 40 | ), 41 | FooterWidget(onNavigate: onNavigate), 42 | ], 43 | ); 44 | }, 45 | ), 46 | ), 47 | ); 48 | } 49 | 50 | Widget _buildTitle(BuildContext context) { 51 | final textColor = 52 | Theme.of(context).brightness == Brightness.dark 53 | ? Colors.white 54 | : Colors.black; 55 | 56 | return Text( 57 | context.los.thank_you_for_download, 58 | textAlign: TextAlign.center, 59 | style: Theme.of(context).textTheme.headlineMedium?.copyWith( 60 | color: textColor, 61 | fontWeight: FontWeight.bold, 62 | ), 63 | ); 64 | } 65 | 66 | Widget _buildDownloadCard( 67 | BuildContext context, 68 | bool isDarkTheme, 69 | bool isSmallScreen, 70 | ) { 71 | final cardBackgroundColor = 72 | isDarkTheme 73 | ? Colors.green[900]!.withValues(alpha: 0.5) 74 | : Colors.green[100]!.withValues(alpha: 0.8); 75 | final textColor = isDarkTheme ? Colors.white : Colors.black; 76 | return Card( 77 | color: cardBackgroundColor, 78 | elevation: 0, 79 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)), 80 | child: Container( 81 | decoration: BoxDecoration( 82 | border: Border.all( 83 | color: isDarkTheme ? Colors.lightGreen[900]! : Colors.black, 84 | width: 2.0, 85 | ), 86 | borderRadius: BorderRadius.circular(16.0), 87 | ), 88 | padding: EdgeInsets.all(isSmallScreen ? 16.0 : 24.0), 89 | child: Column( 90 | mainAxisSize: MainAxisSize.min, 91 | children: [ 92 | if (isWindows) ...[ 93 | _buildWindowsDebug(context), 94 | const SizedBox(height: 24.0), 95 | ], 96 | Text( 97 | context.los.download_start_automatically, 98 | textAlign: TextAlign.center, 99 | style: Theme.of(context).textTheme.bodyLarge?.copyWith( 100 | color: textColor.withValues(alpha: 0.9), 101 | ), 102 | ), 103 | const SizedBox(height: 24.0), 104 | _buildDownloadButton(context), 105 | const SizedBox(height: 16.0), 106 | _buildDirectDownloadLink(context), 107 | ], 108 | ), 109 | ), 110 | ); 111 | } 112 | 113 | Widget _buildWindowsDebug(BuildContext context) { 114 | final isDarkTheme = Theme.of(context).brightness == Brightness.dark; 115 | final textColor = isDarkTheme ? Colors.white : Colors.black; 116 | final linkColor = isDarkTheme ? Colors.pink[300] : Colors.purple; 117 | return Padding( 118 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 119 | child: RichText( 120 | textAlign: TextAlign.center, 121 | text: TextSpan( 122 | style: Theme.of(context).textTheme.bodyLarge?.copyWith( 123 | color: textColor.withValues(alpha: 0.9), 124 | ), 125 | children: [ 126 | TextSpan(text: '${context.los.having_issue} '), 127 | WidgetSpan( 128 | alignment: PlaceholderAlignment.middle, 129 | child: HyperLink( 130 | text: 131 | '[Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170)', 132 | linkStyle: Theme.of(context).textTheme.bodyLarge?.copyWith( 133 | color: linkColor, 134 | fontWeight: FontWeight.bold, 135 | ), 136 | ), 137 | ), 138 | ], 139 | ), 140 | ), 141 | ); 142 | } 143 | 144 | Widget _buildDownloadButton(BuildContext context) { 145 | return ElevatedButton( 146 | onPressed: () { 147 | DownloadHelper.downloadFile(link); 148 | }, 149 | style: ElevatedButton.styleFrom(backgroundColor: Colors.blue), 150 | child: Padding( 151 | padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), 152 | child: Text( 153 | context.los.download_again, 154 | style: Theme.of(context).textTheme.titleMedium?.copyWith( 155 | color: Colors.white, 156 | fontWeight: FontWeight.bold, 157 | ), 158 | ), 159 | ), 160 | ); 161 | } 162 | 163 | Widget _buildDirectDownloadLink(BuildContext context) { 164 | final isDarkTheme = Theme.of(context).brightness == Brightness.dark; 165 | final linkColor = isDarkTheme ? Colors.blue[200] : Colors.blueAccent; 166 | 167 | return TextButton( 168 | onPressed: () { 169 | DownloadHelper.downloadFile(link); 170 | }, 171 | child: Text( 172 | context.los.having_download_problem, 173 | style: TextStyle(color: linkColor, fontWeight: FontWeight.bold), 174 | ), 175 | ); 176 | } 177 | 178 | @override 179 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 180 | super.debugFillProperties(properties); 181 | properties.add(DiagnosticsProperty('isWindows', isWindows)); 182 | properties.add(StringProperty('link', link)); 183 | properties.add( 184 | ObjectFlagProperty.has( 185 | 'onNavigate', 186 | onNavigate, 187 | ), 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Website/lib/screen/footer/footer_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:material_symbols_icons/symbols.dart'; 4 | import '../../extension/context.dart'; 5 | import '../../service/url_helper.dart'; 6 | 7 | class FooterWidget extends StatelessWidget { 8 | const FooterWidget({super.key, required this.onNavigate}); 9 | 10 | final void Function(int index) onNavigate; 11 | 12 | Widget _supported(BuildContext context) { 13 | return Padding( 14 | padding: const EdgeInsets.all(24.0), 15 | child: Column( 16 | crossAxisAlignment: CrossAxisAlignment.start, 17 | spacing: 16.0, 18 | children: [ 19 | Text( 20 | context.los.sen_supported_mod, 21 | style: Theme.of( 22 | context, 23 | ).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold), 24 | ), 25 | _buildHorizontalView(context: context), 26 | ], 27 | ), 28 | ); 29 | } 30 | 31 | Widget _buildHorizontalView({required BuildContext context}) { 32 | List childList(double resolution) => [ 33 | ...[ 34 | 'addendum', 35 | 'altverz', 36 | 'fallen', 37 | 'requiem', 38 | 'resonance', 39 | 'reimagined', 40 | ].map( 41 | (e) => Padding( 42 | padding: const EdgeInsets.only(right: 15.0), 43 | child: Image.asset( 44 | 'assets/images/icons/$e.png', 45 | width: resolution, 46 | height: resolution, 47 | ), 48 | ), 49 | ), 50 | ]; 51 | if (MediaQuery.sizeOf(context).width < 600) { 52 | return SizedBox( 53 | height: 120, 54 | child: CarouselView.weighted( 55 | flexWeights: const [1, 4, 1], 56 | children: childList(120), 57 | ), 58 | ); 59 | } 60 | return SingleChildScrollView( 61 | scrollDirection: Axis.horizontal, 62 | child: Row(children: childList(200)), 63 | ); 64 | } 65 | 66 | Widget _buildSectionTitle(BuildContext context, String title) { 67 | return Text( 68 | title, 69 | style: Theme.of( 70 | context, 71 | ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), 72 | ); 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Container( 78 | color: 79 | Theme.of(context).brightness == Brightness.dark 80 | ? Colors.grey[900] 81 | : Colors.grey[200], 82 | padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0), 83 | child: Column( 84 | spacing: 16.0, 85 | children: [ 86 | _supported(context), 87 | const Divider(), 88 | LayoutBuilder( 89 | builder: (context, constraints) { 90 | return Wrap( 91 | spacing: 32.0, 92 | runSpacing: 16.0, 93 | alignment: WrapAlignment.center, 94 | children: [ 95 | _buildFooterColumn( 96 | context, 97 | title: context.los.quick_links, 98 | links: [ 99 | _buildNavLink(context.los.home, 0), 100 | _buildNavLink(context.los.download, 1), 101 | _buildNavLink(context.los.changelog, 2), 102 | _buildNavLink(context.los.contact, 3), 103 | ], 104 | ), 105 | _buildFooterColumn( 106 | context, 107 | title: context.los.resources, 108 | links: [ 109 | _buildNavigatorLink( 110 | context.los.documentation, 111 | 'https://github.com/harumazzz/Sen.Environment', 112 | ), 113 | _buildNavigatorLink( 114 | context.los.support, 115 | 'https://discord.gg/C2Xr2kaBYJ', 116 | ), 117 | _buildNavigatorLink( 118 | context.los.community_forum, 119 | 'https://discord.com/invite/pvz', 120 | ), 121 | _buildNavigatorLink( 122 | context.los.faq, 123 | 'https://github.com/harumazzz/Sen.Environment/issues', 124 | ), 125 | ], 126 | ), 127 | ], 128 | ); 129 | }, 130 | ), 131 | Row( 132 | mainAxisAlignment: MainAxisAlignment.center, 133 | children: [ 134 | IconButton( 135 | icon: const Icon(Symbols.youtube_activity), 136 | onPressed: () async { 137 | await UrlHelper.launch( 138 | link: 'https://www.youtube.com/@harumavn', 139 | ); 140 | }, 141 | ), 142 | IconButton( 143 | icon: const Icon(Icons.discord_outlined), 144 | onPressed: () async { 145 | await UrlHelper.launch(link: 'https://discord.gg/C2Xr2kaBYJ'); 146 | }, 147 | ), 148 | ], 149 | ), 150 | Text( 151 | '© ${DateTime.now().year} copyright Haruma. All rights reserved.', 152 | style: Theme.of(context).textTheme.bodySmall?.copyWith( 153 | color: 154 | Theme.of(context).brightness == Brightness.dark 155 | ? Colors.grey[400] 156 | : Colors.grey[600], 157 | ), 158 | textAlign: TextAlign.center, 159 | ), 160 | ], 161 | ), 162 | ); 163 | } 164 | 165 | Widget _buildFooterColumn( 166 | BuildContext context, { 167 | required String title, 168 | required List links, 169 | }) { 170 | return Column( 171 | crossAxisAlignment: CrossAxisAlignment.start, 172 | spacing: 8.0, 173 | children: [_buildSectionTitle(context, title), ...links], 174 | ); 175 | } 176 | 177 | Widget _buildNavLink(String text, [int? index]) { 178 | return TextButton( 179 | onPressed: index != null ? () => onNavigate(index) : () {}, 180 | child: Text(text), 181 | ); 182 | } 183 | 184 | Widget _buildNavigatorLink(String text, [String? link]) { 185 | return TextButton( 186 | onPressed: 187 | link != null ? () async => await UrlHelper.launch(link: link) : () {}, 188 | child: Text(text), 189 | ); 190 | } 191 | 192 | @override 193 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 194 | super.debugFillProperties(properties); 195 | properties.add( 196 | ObjectFlagProperty.has( 197 | 'onNavigate', 198 | onNavigate, 199 | ), 200 | ); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Website/lib/screen/extension/extension_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:material_symbols_icons/symbols.dart'; 4 | import 'package:shimmer/shimmer.dart'; 5 | 6 | import '../../extension/context.dart'; 7 | import '../../model/extension.dart'; 8 | import '../../repository/extension_repository.dart'; 9 | import '../../service/service_locator.dart'; 10 | import '../../service/url_helper.dart'; 11 | import '../footer/footer_widget.dart'; 12 | 13 | class ExtensionsPage extends StatefulWidget { 14 | const ExtensionsPage({super.key, required this.onNavigate}); 15 | 16 | final void Function(int index) onNavigate; 17 | 18 | @override 19 | State createState() => _ExtensionsPageState(); 20 | 21 | @override 22 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 23 | super.debugFillProperties(properties); 24 | properties.add( 25 | ObjectFlagProperty.has( 26 | 'onNavigate', 27 | onNavigate, 28 | ), 29 | ); 30 | } 31 | } 32 | 33 | class _ExtensionsPageState extends State { 34 | late ScrollController _scrollController; 35 | late TextEditingController _searchController; 36 | late bool _isFetching; 37 | late bool _hasMore; 38 | late List _extensions; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | _extensions = []; 44 | _isFetching = false; 45 | _hasMore = true; 46 | _searchController = TextEditingController(); 47 | _scrollController = ScrollController()..addListener(_onScroll); 48 | } 49 | 50 | @override 51 | void dispose() { 52 | _searchController.dispose(); 53 | _scrollController.dispose(); 54 | super.dispose(); 55 | } 56 | 57 | Future _fetchExtensions() async { 58 | if (_isFetching || !_hasMore) { 59 | return; 60 | } 61 | 62 | setState(() => _isFetching = true); 63 | 64 | final newExtensions = 65 | await ServiceLocator.instance 66 | .get() 67 | .getExtensions(); 68 | 69 | setState(() { 70 | _extensions.addAll(newExtensions); 71 | _hasMore = ServiceLocator.instance.get().canFetch; 72 | _isFetching = false; 73 | }); 74 | } 75 | 76 | void _onScroll() { 77 | if (_scrollController.position.pixels >= 78 | _scrollController.position.maxScrollExtent - 200 && 79 | !_isFetching && 80 | _hasMore) { 81 | _fetchExtensions(); 82 | } 83 | } 84 | 85 | Future _launchDownload(Extension e) async { 86 | void showSuccess() { 87 | ScaffoldMessenger.of(context).showSnackBar( 88 | SnackBar( 89 | content: Text( 90 | context.los.download_success, 91 | softWrap: true, 92 | overflow: TextOverflow.visible, 93 | ), 94 | width: MediaQuery.sizeOf(context).width > 800 ? 400 : null, 95 | behavior: SnackBarBehavior.floating, 96 | duration: const Duration(milliseconds: 1500), 97 | shape: RoundedRectangleBorder( 98 | borderRadius: BorderRadius.circular(10.0), 99 | ), 100 | ), 101 | ); 102 | } 103 | 104 | await UrlHelper.launch(link: e.downloadLink); 105 | showSuccess(); 106 | } 107 | 108 | Widget _buildShimmer() { 109 | return Shimmer.fromColors( 110 | baseColor: Colors.grey[300]!, 111 | highlightColor: Colors.grey[100]!, 112 | child: Column( 113 | children: [ 114 | _buildShimmerItem(), 115 | _buildShimmerItem(), 116 | _buildShimmerItem(), 117 | _buildShimmerItem(), 118 | _buildShimmerItem(), 119 | ], 120 | ), 121 | ); 122 | } 123 | 124 | Widget _buildShimmerItem() { 125 | return Padding( 126 | padding: const EdgeInsets.symmetric(vertical: 8.0), 127 | child: Card( 128 | shape: RoundedRectangleBorder( 129 | borderRadius: BorderRadius.circular(10.0), 130 | ), 131 | elevation: 3, 132 | child: ListTile( 133 | leading: _buildShimmerBox(width: 40, height: 40), 134 | title: _buildShimmerBox(height: 16), 135 | subtitle: _buildShimmerBox(height: 14), 136 | trailing: _buildShimmerBox(width: 24, height: 24), 137 | ), 138 | ), 139 | ); 140 | } 141 | 142 | Widget _buildShimmerBox({ 143 | double width = double.infinity, 144 | required double height, 145 | }) { 146 | return Container(width: width, height: height, color: Colors.white); 147 | } 148 | 149 | Widget _buildExtensionItem(Extension ext) { 150 | return Card( 151 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), 152 | elevation: 3, 153 | child: ListTile( 154 | leading: const Icon(Symbols.package_2, color: Colors.blueAccent), 155 | title: Text( 156 | ext.extensionTitle, 157 | style: const TextStyle(fontWeight: FontWeight.bold), 158 | ), 159 | subtitle: Column( 160 | crossAxisAlignment: CrossAxisAlignment.start, 161 | spacing: 4.0, 162 | children: [ 163 | Text( 164 | ext.extensionDescription, 165 | maxLines: 8, 166 | overflow: TextOverflow.ellipsis, 167 | ), 168 | Text( 169 | '${context.los.author}: ${ext.extensionAuthor}', 170 | style: TextStyle( 171 | color: Colors.grey[600], 172 | fontStyle: FontStyle.italic, 173 | ), 174 | ), 175 | ], 176 | ), 177 | trailing: IconButton( 178 | tooltip: context.los.download, 179 | icon: const Icon(Symbols.download, color: Colors.green), 180 | onPressed: () => _launchDownload(ext), 181 | ), 182 | ), 183 | ); 184 | } 185 | 186 | Widget _buildSearch() { 187 | return Padding( 188 | padding: const EdgeInsets.all(8.0), 189 | child: Row( 190 | children: [ 191 | Expanded( 192 | child: TextFormField( 193 | controller: _searchController, 194 | decoration: InputDecoration( 195 | prefixIcon: const Icon(Symbols.search), 196 | border: OutlineInputBorder( 197 | borderRadius: BorderRadius.circular(10), 198 | ), 199 | labelText: context.los.modules, 200 | suffix: IconButton( 201 | onPressed: () { 202 | _searchController.clear(); 203 | }, 204 | icon: const Icon(Symbols.clear), 205 | ), 206 | ), 207 | ), 208 | ), 209 | ], 210 | ), 211 | ); 212 | } 213 | 214 | @override 215 | Widget build(BuildContext context) { 216 | return SingleChildScrollView( 217 | controller: _scrollController, 218 | child: Column( 219 | children: [ 220 | _buildSearch(), 221 | Padding( 222 | padding: const EdgeInsets.all(8.0), 223 | child: FutureBuilder( 224 | future: _fetchExtensions(), 225 | builder: (context, snapshot) { 226 | if (snapshot.connectionState == ConnectionState.waiting && 227 | _extensions.isEmpty) { 228 | return _buildShimmer(); 229 | } 230 | return Column( 231 | children: [ 232 | ..._extensions.map((ext) => _buildExtensionItem(ext)), 233 | if (_isFetching) 234 | Padding( 235 | padding: const EdgeInsets.all(8.0), 236 | child: _buildShimmer(), 237 | ), 238 | ], 239 | ); 240 | }, 241 | ), 242 | ), 243 | FooterWidget(onNavigate: widget.onNavigate), 244 | ], 245 | ), 246 | ); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | *.Scripts 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.tlog 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Coverlet is a free, cross platform Code Coverage Tool 147 | coverage*.json 148 | coverage*.xml 149 | coverage*.info 150 | 151 | # Visual Studio code coverage results 152 | *.coverage 153 | *.coveragexml 154 | 155 | # NCrunch 156 | _NCrunch_* 157 | .*crunch*.local.xml 158 | nCrunchTemp_* 159 | 160 | # MightyMoose 161 | *.mm.* 162 | AutoTest.Net/ 163 | 164 | # Web workbench (sass) 165 | .sass-cache/ 166 | 167 | # Installshield output folder 168 | [Ee]xpress/ 169 | 170 | # DocProject is a documentation generator add-in 171 | DocProject/buildhelp/ 172 | DocProject/Help/*.HxT 173 | DocProject/Help/*.HxC 174 | DocProject/Help/*.hhc 175 | DocProject/Help/*.hhk 176 | DocProject/Help/*.hhp 177 | DocProject/Help/Html2 178 | DocProject/Help/html 179 | 180 | # Click-Once directory 181 | publish/ 182 | 183 | # Publish Web Output 184 | *.[Pp]ublish.xml 185 | *.azurePubxml 186 | # Note: Comment the next line if you want to checkin your web deploy settings, 187 | # but database connection strings (with potential passwords) will be unencrypted 188 | *.pubxml 189 | *.publishproj 190 | 191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 192 | # checkin your Azure Web App publish settings, but sensitive information contained 193 | # in these scripts will be unencrypted 194 | PublishScripts/ 195 | 196 | # NuGet Packages 197 | *.nupkg 198 | # NuGet Symbol Packages 199 | *.snupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/[Pp]ackages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/[Pp]ackages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/[Pp]ackages/repositories.config 206 | # NuGet v3's project.json files produces more ignorable files 207 | *.nuget.props 208 | *.nuget.targets 209 | 210 | # Microsoft Azure Build Output 211 | csx/ 212 | *.build.csdef 213 | 214 | # Microsoft Azure Emulator 215 | ecf/ 216 | rcf/ 217 | 218 | # Windows Store app package directories and files 219 | AppPackages/ 220 | BundleArtifacts/ 221 | Package.StoreAssociation.xml 222 | _pkginfo.txt 223 | *.appx 224 | *.appxbundle 225 | *.appxupload 226 | 227 | # Visual Studio cache files 228 | # files ending in .cache can be ignored 229 | *.[Cc]ache 230 | # but keep track of directories ending in .cache 231 | !?*.[Cc]ache/ 232 | 233 | # Others 234 | ClientBin/ 235 | ~$* 236 | *~ 237 | *.dbmdl 238 | *.dbproj.schemaview 239 | *.jfm 240 | *.pfx 241 | *.publishsettings 242 | orleans.codegen.cs 243 | 244 | # Including strong name files can present a security risk 245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 246 | #*.snk 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | Scripts.zip 400 | *.zip 401 | *.yml 402 | .github/workflows/dotnet.yml 403 | *.yml 404 | *.js 405 | Scripts 406 | Shell/Other/Sen.scale.png 407 | Script/test.ts 408 | SharedLibrary 409 | Script/Modules/Executable/PvZ2/WWise/RemoveBNKEvent.ts 410 | Script/Modules/Customization/Language/string.json 411 | Script/test1.ts 412 | Script/External 413 | 414 | # Ignore build output directories 415 | build/ 416 | out/ 417 | .build/ 418 | 419 | # Ignore CMake generated files 420 | CMakeCache.txt 421 | CMakeFiles/ 422 | CMakeScripts/ 423 | Testing/ 424 | Makefile 425 | cmake_install.cmake 426 | install_manifest.txt 427 | compile_commands.json 428 | CTestTestfile.cmake 429 | _deps 430 | 431 | # Ignore other common files and directories 432 | .idea/ 433 | .vs/ 434 | .vscode/ 435 | *.swp 436 | *.swo 437 | *~ 438 | Shell/Properties/launchSettings.json 439 | .prettierrc 440 | WinShell 441 | Documentation 442 | KernelX 443 | Modding/android/app/src/main/jniLibs/arm64-v8a/libKernel.so 444 | Modding/android/app/src/main/jniLibs/arm64-v8a/libKernel.so 445 | Kernel/cmake-build-debug/ 446 | Shell/cmake-build-debug 447 | -------------------------------------------------------------------------------- /Website/lib/screen/about/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:material_symbols_icons/material_symbols_icons.dart'; 4 | import '../../extension/context.dart'; 5 | import '../footer/footer_widget.dart'; 6 | 7 | class AboutPage extends StatelessWidget { 8 | const AboutPage({super.key, required this.onNavigate}); 9 | 10 | final void Function(int index) onNavigate; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final isDarkTheme = Theme.of(context).brightness == Brightness.dark; 15 | final screenWidth = MediaQuery.sizeOf(context).width; 16 | final isSmallScreen = screenWidth < 600; 17 | 18 | return SingleChildScrollView( 19 | child: Column( 20 | children: [ 21 | Padding( 22 | padding: EdgeInsets.symmetric( 23 | horizontal: isSmallScreen ? 12.0 : 24.0, 24 | vertical: 16.0, 25 | ), 26 | child: Column( 27 | children: [ 28 | _buildHeader(context, isSmallScreen), 29 | const SizedBox(height: 24.0), 30 | _infoCard( 31 | icon: Symbols.info, 32 | title: context.los.about, 33 | content: Text( 34 | context.los.sen_promotional_description, 35 | style: TextStyle(fontSize: isSmallScreen ? 16.0 : 18.0), 36 | ), 37 | isSmallScreen: isSmallScreen, 38 | ), 39 | _infoCard( 40 | icon: Symbols.open_in_new, 41 | title: context.los.open_source_project, 42 | content: Text( 43 | context.los.open_source_project_description, 44 | style: TextStyle(fontSize: isSmallScreen ? 16.0 : 18.0), 45 | ), 46 | isSmallScreen: isSmallScreen, 47 | ), 48 | _infoCard( 49 | icon: Symbols.devices, 50 | title: context.los.platform_support, 51 | content: _bulletPoints( 52 | [ 53 | 'Windows 10+ x64', 54 | 'Linux x64', 55 | 'Macintosh x64', 56 | 'iOS 10.0+', 57 | 'Android 7.0+', 58 | ], 59 | isDarkTheme, 60 | isSmallScreen, 61 | ), 62 | isSmallScreen: isSmallScreen, 63 | ), 64 | _infoCard( 65 | icon: Symbols.category, 66 | title: 'Modules', 67 | content: _bulletPoints( 68 | [ 69 | 'Kernel - ${context.los.kernel_description}', 70 | 'Shell - ${context.los.shell_description}', 71 | 'Script - ${context.los.script_description}', 72 | 'Modding - ${context.los.modding_description}', 73 | ], 74 | isDarkTheme, 75 | isSmallScreen, 76 | ), 77 | isSmallScreen: isSmallScreen, 78 | ), 79 | _infoCard( 80 | icon: Symbols.code, 81 | title: context.los.technology, 82 | content: _detailedBulletPoints( 83 | (() { 84 | return [ 85 | { 86 | 'title': 'CMake', 87 | 'description': context.los.cmake_description, 88 | }, 89 | { 90 | 'title': 'Flutter', 91 | 'description': context.los.flutter_description, 92 | }, 93 | ]; 94 | })(), 95 | isDarkTheme, 96 | ), 97 | isSmallScreen: isSmallScreen, 98 | ), 99 | ], 100 | ), 101 | ), 102 | FooterWidget(onNavigate: onNavigate), 103 | ], 104 | ), 105 | ); 106 | } 107 | 108 | Widget _buildHeader(BuildContext context, bool isSmallScreen) { 109 | return Container( 110 | width: double.infinity, 111 | padding: EdgeInsets.symmetric( 112 | vertical: isSmallScreen ? 24.0 : 40.0, 113 | horizontal: isSmallScreen ? 16.0 : 24.0, 114 | ), 115 | decoration: BoxDecoration( 116 | gradient: LinearGradient( 117 | colors: 118 | Theme.of(context).brightness == Brightness.light 119 | ? [Colors.blueAccent, Colors.lightBlue.shade100] 120 | : [Colors.blueAccent, Colors.pinkAccent.shade100], 121 | begin: Alignment.topLeft, 122 | end: Alignment.bottomRight, 123 | ), 124 | borderRadius: BorderRadius.circular(16.0), 125 | ), 126 | child: Column( 127 | mainAxisAlignment: MainAxisAlignment.center, 128 | children: [ 129 | Image.asset( 130 | 'assets/images/logo.png', 131 | width: isSmallScreen ? 80 : 120, 132 | height: isSmallScreen ? 80 : 120, 133 | fit: BoxFit.contain, 134 | ), 135 | const SizedBox(height: 16.0), 136 | Text( 137 | 'Sen.Environment', 138 | textAlign: TextAlign.center, 139 | style: TextStyle( 140 | fontSize: isSmallScreen ? 28.0 : 36.0, 141 | fontWeight: FontWeight.bold, 142 | color: Colors.white, 143 | ), 144 | ), 145 | const SizedBox(height: 8.0), 146 | Text( 147 | context.los.sen_promotional_title, 148 | textAlign: TextAlign.center, 149 | style: TextStyle( 150 | fontSize: isSmallScreen ? 16.0 : 18.0, 151 | color: Colors.white70, 152 | ), 153 | ), 154 | ], 155 | ), 156 | ); 157 | } 158 | 159 | Widget _asWrappable({ 160 | required bool isSmallScreen, 161 | required List children, 162 | }) { 163 | return isSmallScreen 164 | ? Wrap( 165 | crossAxisAlignment: WrapCrossAlignment.center, 166 | spacing: 12.0, 167 | children: children, 168 | ) 169 | : Row(spacing: 12.0, children: children); 170 | } 171 | 172 | Widget _infoCard({ 173 | required IconData icon, 174 | required String title, 175 | required Widget content, 176 | required bool isSmallScreen, 177 | }) { 178 | return Card( 179 | elevation: 4.0, 180 | margin: const EdgeInsets.symmetric(vertical: 12.0), 181 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), 182 | child: Padding( 183 | padding: const EdgeInsets.all(16.0), 184 | child: Column( 185 | crossAxisAlignment: CrossAxisAlignment.start, 186 | spacing: 12.0, 187 | children: [ 188 | _asWrappable( 189 | isSmallScreen: isSmallScreen, 190 | children: [ 191 | Icon(icon, size: 28.0, color: Colors.blueAccent), 192 | Text( 193 | title, 194 | style: TextStyle( 195 | fontSize: isSmallScreen ? 16.0 : 18.0, 196 | fontWeight: FontWeight.bold, 197 | ), 198 | ), 199 | ], 200 | ), 201 | content, 202 | ], 203 | ), 204 | ), 205 | ); 206 | } 207 | 208 | Widget _bulletPoints( 209 | List points, 210 | bool isDarkTheme, 211 | bool isSmallScreen, 212 | ) { 213 | return Column( 214 | crossAxisAlignment: CrossAxisAlignment.start, 215 | children: 216 | points.map((point) { 217 | return Padding( 218 | padding: const EdgeInsets.symmetric(vertical: 4.0), 219 | child: Row( 220 | crossAxisAlignment: CrossAxisAlignment.start, 221 | children: [ 222 | Text( 223 | '• ', 224 | style: TextStyle(fontSize: isSmallScreen ? 14.0 : 16.0), 225 | ), 226 | Expanded( 227 | child: Text( 228 | point, 229 | style: TextStyle( 230 | fontSize: isSmallScreen ? 14.0 : 16.0, 231 | color: isDarkTheme ? Colors.white : Colors.black87, 232 | ), 233 | ), 234 | ), 235 | ], 236 | ), 237 | ); 238 | }).toList(), 239 | ); 240 | } 241 | 242 | Widget _detailedBulletPoints( 243 | List> items, 244 | bool isDarkTheme, 245 | ) { 246 | return Column( 247 | crossAxisAlignment: CrossAxisAlignment.start, 248 | children: 249 | items.map((item) { 250 | return Padding( 251 | padding: const EdgeInsets.symmetric(vertical: 8.0), 252 | child: Column( 253 | crossAxisAlignment: CrossAxisAlignment.start, 254 | spacing: 4.0, 255 | children: [ 256 | Text( 257 | item['title']!, 258 | style: const TextStyle( 259 | fontSize: 16.0, 260 | fontWeight: FontWeight.bold, 261 | ), 262 | ), 263 | Text( 264 | item['description']!, 265 | style: TextStyle( 266 | fontSize: 14.0, 267 | color: isDarkTheme ? Colors.white54 : Colors.black54, 268 | ), 269 | ), 270 | ], 271 | ), 272 | ); 273 | }).toList(), 274 | ); 275 | } 276 | 277 | @override 278 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 279 | super.debugFillProperties(properties); 280 | properties.add( 281 | ObjectFlagProperty.has( 282 | 'onNavigate', 283 | onNavigate, 284 | ), 285 | ); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /Website/lib/screen/download/download_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | import '../../extension/context.dart'; 5 | import '../../model/github.dart'; 6 | import '../../repository/github_repository.dart'; 7 | import '../footer/footer_widget.dart'; 8 | import '../../service/download_helper.dart'; 9 | import '../../service/service_locator.dart'; 10 | import 'download_error.dart'; 11 | 12 | class DownloadPage extends StatefulWidget { 13 | const DownloadPage({super.key, required this.onNavigate}); 14 | final void Function(int index) onNavigate; 15 | 16 | @override 17 | State createState() => _DownloadPageState(); 18 | 19 | @override 20 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 21 | super.debugFillProperties(properties); 22 | properties.add( 23 | ObjectFlagProperty.has( 24 | 'onNavigate', 25 | onNavigate, 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | class _DownloadPageState extends State 32 | with AutomaticKeepAliveClientMixin { 33 | late Future _future; 34 | late int _totalDownloadCount; 35 | 36 | @override 37 | void initState() { 38 | _future = ServiceLocator.instance.get().getRelease(); 39 | _totalDownloadCount = 0; 40 | super.initState(); 41 | } 42 | 43 | void _calculateTotalDownloadCount(GitHub response) { 44 | if (_totalDownloadCount != 0) { 45 | return; 46 | } 47 | for (final e in response.assets!) { 48 | _totalDownloadCount += e.downloadCount!; 49 | } 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | super.build(context); 55 | final screenWidth = MediaQuery.sizeOf(context).width; 56 | final isSmallScreen = screenWidth < 600; 57 | 58 | return FutureBuilder( 59 | future: _future, 60 | builder: (context, snapshot) { 61 | if (snapshot.connectionState == ConnectionState.waiting) { 62 | return const Center(child: CircularProgressIndicator.adaptive()); 63 | } else if (snapshot.hasError) { 64 | return const DownloadError(); 65 | } else if (snapshot.hasData) { 66 | final data = snapshot.data!; 67 | _calculateTotalDownloadCount(data); 68 | 69 | return SingleChildScrollView( 70 | child: Column( 71 | children: [ 72 | Padding( 73 | padding: EdgeInsets.symmetric( 74 | horizontal: isSmallScreen ? 12.0 : 24.0, 75 | ), 76 | child: Column( 77 | spacing: 16.0, 78 | children: [ 79 | _displayLogo(isSmallScreen), 80 | _introduceText(isSmallScreen), 81 | _subtitleText(isSmallScreen), 82 | if (isSmallScreen) 83 | Column( 84 | mainAxisAlignment: MainAxisAlignment.center, 85 | spacing: 24.0, 86 | children: [ 87 | _buildWindowsInstallation(isSmallScreen), 88 | _buildAndroidInstallation(isSmallScreen), 89 | ], 90 | ) 91 | else 92 | Row( 93 | mainAxisAlignment: MainAxisAlignment.center, 94 | spacing: 32.0, 95 | children: [ 96 | Expanded( 97 | child: _buildWindowsInstallation(isSmallScreen), 98 | ), 99 | Expanded( 100 | child: _buildAndroidInstallation(isSmallScreen), 101 | ), 102 | ], 103 | ), 104 | _agreeLicense(isSmallScreen), 105 | _downloadCount(isSmallScreen), 106 | const SizedBox(height: 16.0), 107 | ], 108 | ), 109 | ), 110 | FooterWidget(onNavigate: widget.onNavigate), 111 | ], 112 | ), 113 | ); 114 | } else { 115 | return const SizedBox.shrink(); 116 | } 117 | }, 118 | ); 119 | } 120 | 121 | Widget _introduceText(bool isSmallScreen) { 122 | return Center( 123 | child: Text( 124 | 'Sen: Environment', 125 | textAlign: TextAlign.center, 126 | style: Theme.of(context).textTheme.displaySmall?.copyWith( 127 | fontSize: isSmallScreen ? 20 : 28, 128 | fontWeight: FontWeight.bold, 129 | ), 130 | ), 131 | ); 132 | } 133 | 134 | Widget _subtitleText(bool isSmallScreen) { 135 | return Center( 136 | child: Text( 137 | context.los.sen_title, 138 | maxLines: 4, 139 | textAlign: TextAlign.center, 140 | style: Theme.of(context).textTheme.headlineSmall?.copyWith( 141 | fontSize: isSmallScreen ? 16 : 20, 142 | fontWeight: FontWeight.bold, 143 | ), 144 | ), 145 | ); 146 | } 147 | 148 | Widget _agreeLicense(bool isSmallScreen) { 149 | return Center( 150 | child: Text( 151 | context.los.sen_agreement, 152 | textAlign: TextAlign.center, 153 | style: Theme.of(context).textTheme.labelLarge?.copyWith( 154 | fontSize: isSmallScreen ? 14 : 16, 155 | color: 156 | Theme.of(context).brightness == Brightness.dark 157 | ? Colors.grey[300] 158 | : Colors.grey[700], 159 | ), 160 | ), 161 | ); 162 | } 163 | 164 | Widget _downloadCount(bool isSmallScreen) { 165 | return Center( 166 | child: Text( 167 | context.los.total_sen_users(_totalDownloadCount), 168 | textAlign: TextAlign.center, 169 | style: Theme.of(context).textTheme.labelLarge?.copyWith( 170 | fontSize: isSmallScreen ? 14 : 16, 171 | color: 172 | Theme.of(context).brightness == Brightness.dark 173 | ? Colors.grey[300] 174 | : Colors.grey[700], 175 | ), 176 | ), 177 | ); 178 | } 179 | 180 | Widget _displayLogo(bool isSmallScreen) { 181 | return Center( 182 | child: Image.asset( 183 | 'assets/images/logo.png', 184 | width: isSmallScreen ? 100 : 150, 185 | height: isSmallScreen ? 100 : 150, 186 | ), 187 | ); 188 | } 189 | 190 | Widget _buildWindowsInstallation(bool isSmallScreen) { 191 | const link = 192 | 'https://github.com/Haruma-VN/Sen.Environment/releases/download/release/win-x64.zip'; 193 | return _buildDownloadCard( 194 | title: 'Windows 10+', 195 | system: 'x64', 196 | fileType: 'Zip', 197 | imagePath: 'assets/images/logos/windows.png', 198 | buttonText: context.los.download_for_windows, 199 | link: link, 200 | isWindows: true, 201 | isSmallScreen: isSmallScreen, 202 | ); 203 | } 204 | 205 | Widget _buildAndroidInstallation(bool isSmallScreen) { 206 | const link = 207 | 'https://github.com/Haruma-VN/Sen.Environment/releases/download/release/android-arm64-v8a.zip'; 208 | return _buildDownloadCard( 209 | title: 'Android 7.0+', 210 | system: 'arm-64', 211 | fileType: 'Zip', 212 | imagePath: 'assets/images/logos/android.png', 213 | buttonText: context.los.download_for_android, 214 | link: link, 215 | isWindows: false, 216 | isSmallScreen: isSmallScreen, 217 | ); 218 | } 219 | 220 | Widget _buildDownloadCard({ 221 | required String title, 222 | required String system, 223 | required String fileType, 224 | required String imagePath, 225 | required String buttonText, 226 | required String link, 227 | required bool isWindows, 228 | required bool isSmallScreen, 229 | }) { 230 | return Column( 231 | spacing: 16.0, 232 | children: [ 233 | Image.asset( 234 | imagePath, 235 | width: isSmallScreen ? 80 : 100, 236 | height: isSmallScreen ? 80 : 100, 237 | ), 238 | ElevatedButton( 239 | style: ElevatedButton.styleFrom( 240 | backgroundColor: Colors.blue, 241 | padding: EdgeInsets.symmetric( 242 | vertical: isSmallScreen ? 12.0 : 20.0, 243 | horizontal: isSmallScreen ? 16.0 : 24.0, 244 | ), 245 | ), 246 | onPressed: () { 247 | context.go( 248 | '/download/success', 249 | extra: { 250 | 'isWindows': isWindows, 251 | 'link': link, 252 | 'onNavigate': (int index) { 253 | context.go(['/', '/download', '/changelog', '/about'][index]); 254 | }, 255 | }, 256 | ); 257 | DownloadHelper.downloadFile(link); 258 | }, 259 | child: Text( 260 | buttonText, 261 | style: Theme.of( 262 | context, 263 | ).textTheme.titleMedium?.copyWith(color: Colors.white), 264 | ), 265 | ), 266 | Card( 267 | child: Padding( 268 | padding: const EdgeInsets.all(12.0), 269 | child: Table( 270 | columnWidths: const { 271 | 0: FixedColumnWidth(150), 272 | 1: FixedColumnWidth(100), 273 | }, 274 | children: [ 275 | _buildTableRow('${context.los.version}:', title), 276 | _buildTableRow('${context.los.system}:', system), 277 | _buildTableRow('${context.los.file_type}:', fileType), 278 | ], 279 | ), 280 | ), 281 | ), 282 | ], 283 | ); 284 | } 285 | 286 | TableRow _buildTableRow(String label, String value) { 287 | return TableRow( 288 | children: [ 289 | Padding( 290 | padding: const EdgeInsets.symmetric(vertical: 4.0), 291 | child: Text(label), 292 | ), 293 | Padding( 294 | padding: const EdgeInsets.symmetric(vertical: 4.0), 295 | child: Text(value), 296 | ), 297 | ], 298 | ); 299 | } 300 | 301 | @override 302 | bool get wantKeepAlive => true; 303 | } 304 | -------------------------------------------------------------------------------- /Website/lib/screen/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:material_symbols_icons/material_symbols_icons.dart'; 4 | import '../../extension/context.dart'; 5 | import '../footer/footer_widget.dart'; 6 | import '../../service/url_helper.dart'; 7 | 8 | class HomePage extends StatefulWidget { 9 | const HomePage({super.key, required this.onNavigate}); 10 | 11 | final void Function(int index) onNavigate; 12 | 13 | @override 14 | State createState() => _HomePageState(); 15 | 16 | @override 17 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 18 | super.debugFillProperties(properties); 19 | properties.add( 20 | ObjectFlagProperty.has( 21 | 'onNavigate', 22 | onNavigate, 23 | ), 24 | ); 25 | } 26 | } 27 | 28 | class _HomePageState extends State { 29 | @override 30 | void initState() { 31 | super.initState(); 32 | } 33 | 34 | Widget _displayLogo(bool isSmallScreen) { 35 | return Center( 36 | child: Image.asset( 37 | 'assets/images/logo.png', 38 | width: isSmallScreen ? 100 : 150, 39 | height: isSmallScreen ? 100 : 150, 40 | ), 41 | ); 42 | } 43 | 44 | @override 45 | void didChangeDependencies() { 46 | super.didChangeDependencies(); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | super.dispose(); 52 | } 53 | 54 | Widget _introduceText(BuildContext context) { 55 | return Center( 56 | child: Padding( 57 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 58 | child: LayoutBuilder( 59 | builder: (context, constraints) { 60 | double fontSize = constraints.maxWidth < 400 ? 20 : 24; 61 | return Text( 62 | context.los.build_your_mod_faster, 63 | style: Theme.of(context).textTheme.displaySmall?.copyWith( 64 | fontSize: fontSize, 65 | fontWeight: FontWeight.bold, 66 | ), 67 | textAlign: TextAlign.center, 68 | softWrap: true, 69 | ); 70 | }, 71 | ), 72 | ), 73 | ); 74 | } 75 | 76 | Widget _displayText(BuildContext context) { 77 | return Center( 78 | child: Padding( 79 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 80 | child: LayoutBuilder( 81 | builder: (context, constraints) { 82 | double fontSize = constraints.maxWidth < 350 ? 16 : 20; 83 | return Text( 84 | context.los.sen_first_description, 85 | maxLines: 4, 86 | softWrap: true, 87 | textAlign: TextAlign.center, 88 | style: Theme.of(context).textTheme.headlineSmall?.copyWith( 89 | fontSize: fontSize, 90 | fontWeight: FontWeight.bold, 91 | color: 92 | Theme.of(context).brightness == Brightness.dark 93 | ? Colors.grey[300] 94 | : Colors.grey[700], 95 | ), 96 | ); 97 | }, 98 | ), 99 | ), 100 | ); 101 | } 102 | 103 | Widget _buttonRow() { 104 | return Center( 105 | child: Padding( 106 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 107 | child: Wrap( 108 | spacing: 8.0, 109 | runSpacing: 8.0, 110 | alignment: WrapAlignment.center, 111 | children: [_downloadButton(), _viewLogButton()], 112 | ), 113 | ), 114 | ); 115 | } 116 | 117 | Widget _downloadButton() { 118 | return _responsiveButton( 119 | text: context.los.download_now, 120 | color: Colors.blue, 121 | icon: Symbols.download, 122 | onPressed: () { 123 | widget.onNavigate(1); 124 | }, 125 | ); 126 | } 127 | 128 | Widget _viewLogButton() { 129 | return _responsiveButton( 130 | text: context.los.discord_server, 131 | icon: Icons.discord, 132 | color: const Color(0xFF5865F2), 133 | onPressed: () async { 134 | await UrlHelper.launch(link: 'https://discord.gg/C2Xr2kaBYJ'); 135 | }, 136 | ); 137 | } 138 | 139 | Widget _responsiveButton({ 140 | required String text, 141 | required Color color, 142 | required IconData icon, 143 | required VoidCallback onPressed, 144 | }) { 145 | return ElevatedButton( 146 | style: ElevatedButton.styleFrom(backgroundColor: color), 147 | onPressed: onPressed, 148 | child: Padding( 149 | padding: EdgeInsets.symmetric( 150 | vertical: 12.0, 151 | horizontal: MediaQuery.sizeOf(context).width < 350 ? 8.0 : 16.0, 152 | ), 153 | child: Row( 154 | spacing: 10.0, 155 | crossAxisAlignment: CrossAxisAlignment.start, 156 | mainAxisAlignment: MainAxisAlignment.center, 157 | mainAxisSize: MainAxisSize.min, 158 | children: [ 159 | Icon(icon, size: 24.0, color: Colors.white), 160 | Text( 161 | text, 162 | style: Theme.of(context).textTheme.titleMedium?.copyWith( 163 | color: Colors.white, 164 | fontWeight: FontWeight.bold, 165 | ), 166 | textAlign: TextAlign.center, 167 | ), 168 | ], 169 | ), 170 | ), 171 | ); 172 | } 173 | 174 | Widget _buildDisplayPreviewImage(BuildContext context) { 175 | if (MediaQuery.sizeOf(context).width < 600) { 176 | return Image.asset( 177 | Theme.of(context).brightness == Brightness.dark 178 | ? 'assets/images/dark/phone.jpg' 179 | : 'assets/images/light/phone.jpg', 180 | ); 181 | } 182 | return Image.asset( 183 | Theme.of(context).brightness == Brightness.dark 184 | ? 'assets/images/dark/launcher.png' 185 | : 'assets/images/light/launcher.png', 186 | ); 187 | } 188 | 189 | Widget _previewImage(BuildContext context) { 190 | return Center( 191 | child: Padding( 192 | padding: const EdgeInsets.all(16.0), 193 | child: ClipRRect( 194 | borderRadius: const BorderRadius.all(Radius.circular(20.0)), 195 | child: _buildDisplayPreviewImage(context), 196 | ), 197 | ), 198 | ); 199 | } 200 | 201 | Widget _descriptionImage(BuildContext context) { 202 | if (MediaQuery.sizeOf(context).width > 600) { 203 | return Image.asset('assets/images/terminal.png', fit: BoxFit.cover); 204 | } 205 | return Image.asset('assets/images/phone_view.jpg', fit: BoxFit.cover); 206 | } 207 | 208 | Widget _description(BuildContext context) { 209 | return Padding( 210 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 211 | child: LayoutBuilder( 212 | builder: (context, constraints) { 213 | bool isSmallScreen = constraints.maxWidth < 600; 214 | return Wrap( 215 | spacing: 16.0, 216 | runSpacing: 16.0, 217 | crossAxisAlignment: WrapCrossAlignment.center, 218 | alignment: WrapAlignment.center, 219 | children: [ 220 | SizedBox( 221 | width: 222 | isSmallScreen 223 | ? double.infinity 224 | : constraints.maxWidth * 0.6, 225 | child: Column( 226 | crossAxisAlignment: CrossAxisAlignment.start, 227 | spacing: 12.0, 228 | children: [ 229 | Text( 230 | context.los.why_sen, 231 | style: Theme.of(context).textTheme.headlineSmall 232 | ?.copyWith(fontWeight: FontWeight.bold), 233 | ), 234 | _buildText(context.los.improve_mod_production), 235 | _buildRichText( 236 | '${context.los.community_support}: ', 237 | '${context.los.sen_big_community_support}.', 238 | ), 239 | _buildRichText( 240 | '${context.los.quality_assurance}: ', 241 | '${context.los.quality_ensurance_description}.', 242 | ), 243 | ], 244 | ), 245 | ), 246 | if (!isSmallScreen) const SizedBox(width: 16.0), 247 | if (!isSmallScreen) 248 | SizedBox( 249 | width: 250 | isSmallScreen 251 | ? constraints.maxWidth * 0.8 252 | : constraints.maxWidth * 0.3, 253 | child: ClipRRect( 254 | borderRadius: BorderRadius.circular(20.0), 255 | child: _descriptionImage(context), 256 | ), 257 | ), 258 | ], 259 | ); 260 | }, 261 | ), 262 | ); 263 | } 264 | 265 | Widget _buildText(String text) { 266 | return Text( 267 | text, 268 | style: Theme.of(context).textTheme.bodyLarge?.copyWith( 269 | color: 270 | Theme.of(context).brightness == Brightness.dark 271 | ? Colors.grey[300] 272 | : Colors.grey[700], 273 | ), 274 | softWrap: true, 275 | ); 276 | } 277 | 278 | Widget _buildRichText(String boldText, String normalText) { 279 | return Text.rich( 280 | TextSpan( 281 | children: [ 282 | TextSpan( 283 | text: boldText, 284 | style: Theme.of(context).textTheme.bodyLarge?.copyWith( 285 | fontWeight: FontWeight.bold, 286 | color: 287 | Theme.of(context).brightness == Brightness.dark 288 | ? Colors.grey[300] 289 | : Colors.grey[700], 290 | ), 291 | ), 292 | TextSpan( 293 | text: normalText, 294 | style: Theme.of(context).textTheme.bodyLarge, 295 | ), 296 | ], 297 | ), 298 | softWrap: true, 299 | ); 300 | } 301 | 302 | @override 303 | Widget build(BuildContext context) { 304 | return SingleChildScrollView( 305 | child: Column( 306 | crossAxisAlignment: CrossAxisAlignment.start, 307 | children: [ 308 | Padding( 309 | padding: const EdgeInsets.all(8.0), 310 | child: Column( 311 | crossAxisAlignment: CrossAxisAlignment.start, 312 | spacing: 16.0, 313 | children: [ 314 | _displayLogo(MediaQuery.sizeOf(context).width < 600), 315 | _introduceText(context), 316 | _displayText(context), 317 | _buttonRow(), 318 | _previewImage(context), 319 | const SizedBox(height: 16.0), 320 | _description(context), 321 | const SizedBox(height: 16.0), 322 | ], 323 | ), 324 | ), 325 | FooterWidget(onNavigate: widget.onNavigate), 326 | ], 327 | ), 328 | ); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /Website/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _flutterfire_internals: 5 | dependency: transitive 6 | description: 7 | name: _flutterfire_internals 8 | sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "1.3.53" 12 | args: 13 | dependency: transitive 14 | description: 15 | name: args 16 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.7.0" 20 | async: 21 | dependency: transitive 22 | description: 23 | name: async 24 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.12.0" 28 | boolean_selector: 29 | dependency: transitive 30 | description: 31 | name: boolean_selector 32 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.1.2" 36 | chalkdart: 37 | dependency: transitive 38 | description: 39 | name: chalkdart 40 | sha256: e7cfcc9a9d9546843304c1ff87fe0696c7eb82ee70e6df63f555f321b15a40d8 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.3.3" 44 | characters: 45 | dependency: transitive 46 | description: 47 | name: characters 48 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.4.0" 52 | clock: 53 | dependency: transitive 54 | description: 55 | name: clock 56 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.1.2" 60 | cloud_firestore: 61 | dependency: "direct main" 62 | description: 63 | name: cloud_firestore 64 | sha256: "6b5d0ca6b62830ca5bcf98c5e0c882df32dea3cb523b635db3626333e1c29090" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "5.6.5" 68 | cloud_firestore_platform_interface: 69 | dependency: transitive 70 | description: 71 | name: cloud_firestore_platform_interface 72 | sha256: "149c7d2d634178aff8e25eba6cbbbc76bd92f37e0e63727a31952c72bbcb77fb" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "6.6.5" 76 | cloud_firestore_web: 77 | dependency: transitive 78 | description: 79 | name: cloud_firestore_web 80 | sha256: "05a3c02a7edb3fadeb3f14f491c3a0bbad3ea2c9f22842acbf1d73bceeb93e77" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "4.4.5" 84 | collection: 85 | dependency: transitive 86 | description: 87 | name: collection 88 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.19.1" 92 | cupertino_icons: 93 | dependency: "direct main" 94 | description: 95 | name: cupertino_icons 96 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "1.0.8" 100 | dio: 101 | dependency: "direct main" 102 | description: 103 | name: dio 104 | sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "5.8.0+1" 108 | dio_web_adapter: 109 | dependency: transitive 110 | description: 111 | name: dio_web_adapter 112 | sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "2.1.1" 116 | equatable: 117 | dependency: "direct main" 118 | description: 119 | name: equatable 120 | sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "2.0.7" 124 | fake_async: 125 | dependency: transitive 126 | description: 127 | name: fake_async 128 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "1.3.2" 132 | file: 133 | dependency: transitive 134 | description: 135 | name: file 136 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "7.0.1" 140 | firebase_core: 141 | dependency: "direct main" 142 | description: 143 | name: firebase_core 144 | sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "3.12.1" 148 | firebase_core_platform_interface: 149 | dependency: transitive 150 | description: 151 | name: firebase_core_platform_interface 152 | sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "5.4.0" 156 | firebase_core_web: 157 | dependency: transitive 158 | description: 159 | name: firebase_core_web 160 | sha256: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "2.21.1" 164 | flutter: 165 | dependency: "direct main" 166 | description: flutter 167 | source: sdk 168 | version: "0.0.0" 169 | flutter_dotenv: 170 | dependency: "direct main" 171 | description: 172 | name: flutter_dotenv 173 | sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b 174 | url: "https://pub.dev" 175 | source: hosted 176 | version: "5.2.1" 177 | flutter_lints: 178 | dependency: "direct dev" 179 | description: 180 | name: flutter_lints 181 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 182 | url: "https://pub.dev" 183 | source: hosted 184 | version: "5.0.0" 185 | flutter_localizations: 186 | dependency: "direct main" 187 | description: flutter 188 | source: sdk 189 | version: "0.0.0" 190 | flutter_test: 191 | dependency: "direct dev" 192 | description: flutter 193 | source: sdk 194 | version: "0.0.0" 195 | flutter_web_plugins: 196 | dependency: "direct main" 197 | description: flutter 198 | source: sdk 199 | version: "0.0.0" 200 | get_it: 201 | dependency: "direct main" 202 | description: 203 | name: get_it 204 | sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 205 | url: "https://pub.dev" 206 | source: hosted 207 | version: "8.0.3" 208 | glob: 209 | dependency: transitive 210 | description: 211 | name: glob 212 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de 213 | url: "https://pub.dev" 214 | source: hosted 215 | version: "2.1.3" 216 | go_router: 217 | dependency: "direct main" 218 | description: 219 | name: go_router 220 | sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 221 | url: "https://pub.dev" 222 | source: hosted 223 | version: "14.8.1" 224 | http_parser: 225 | dependency: transitive 226 | description: 227 | name: http_parser 228 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 229 | url: "https://pub.dev" 230 | source: hosted 231 | version: "4.1.2" 232 | hyperlink: 233 | dependency: "direct main" 234 | description: 235 | name: hyperlink 236 | sha256: "2c750165b76924b56e7a28b7f7f17252ba701309546d64900814d41c807715a4" 237 | url: "https://pub.dev" 238 | source: hosted 239 | version: "0.0.5" 240 | intl: 241 | dependency: transitive 242 | description: 243 | name: intl 244 | sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf 245 | url: "https://pub.dev" 246 | source: hosted 247 | version: "0.19.0" 248 | leak_tracker: 249 | dependency: transitive 250 | description: 251 | name: leak_tracker 252 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec 253 | url: "https://pub.dev" 254 | source: hosted 255 | version: "10.0.8" 256 | leak_tracker_flutter_testing: 257 | dependency: transitive 258 | description: 259 | name: leak_tracker_flutter_testing 260 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 261 | url: "https://pub.dev" 262 | source: hosted 263 | version: "3.0.9" 264 | leak_tracker_testing: 265 | dependency: transitive 266 | description: 267 | name: leak_tracker_testing 268 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 269 | url: "https://pub.dev" 270 | source: hosted 271 | version: "3.0.1" 272 | lints: 273 | dependency: transitive 274 | description: 275 | name: lints 276 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 277 | url: "https://pub.dev" 278 | source: hosted 279 | version: "5.1.1" 280 | logging: 281 | dependency: transitive 282 | description: 283 | name: logging 284 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 285 | url: "https://pub.dev" 286 | source: hosted 287 | version: "1.3.0" 288 | matcher: 289 | dependency: transitive 290 | description: 291 | name: matcher 292 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 293 | url: "https://pub.dev" 294 | source: hosted 295 | version: "0.12.17" 296 | material_color_utilities: 297 | dependency: transitive 298 | description: 299 | name: material_color_utilities 300 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 301 | url: "https://pub.dev" 302 | source: hosted 303 | version: "0.11.1" 304 | material_symbols_icons: 305 | dependency: "direct main" 306 | description: 307 | name: material_symbols_icons 308 | sha256: "5f359722bb52bc91c4351d7e575268a9d944a0c2d3b19befd70e4ff60cdbfc9b" 309 | url: "https://pub.dev" 310 | source: hosted 311 | version: "4.2810.1" 312 | meta: 313 | dependency: transitive 314 | description: 315 | name: meta 316 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 317 | url: "https://pub.dev" 318 | source: hosted 319 | version: "1.16.0" 320 | path: 321 | dependency: transitive 322 | description: 323 | name: path 324 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "1.9.1" 328 | plugin_platform_interface: 329 | dependency: transitive 330 | description: 331 | name: plugin_platform_interface 332 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "2.1.8" 336 | shimmer: 337 | dependency: "direct main" 338 | description: 339 | name: shimmer 340 | sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "3.0.0" 344 | sky_engine: 345 | dependency: transitive 346 | description: flutter 347 | source: sdk 348 | version: "0.0.0" 349 | source_span: 350 | dependency: transitive 351 | description: 352 | name: source_span 353 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 354 | url: "https://pub.dev" 355 | source: hosted 356 | version: "1.10.1" 357 | stack_trace: 358 | dependency: transitive 359 | description: 360 | name: stack_trace 361 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 362 | url: "https://pub.dev" 363 | source: hosted 364 | version: "1.12.1" 365 | stream_channel: 366 | dependency: transitive 367 | description: 368 | name: stream_channel 369 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 370 | url: "https://pub.dev" 371 | source: hosted 372 | version: "2.1.4" 373 | string_scanner: 374 | dependency: transitive 375 | description: 376 | name: string_scanner 377 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 378 | url: "https://pub.dev" 379 | source: hosted 380 | version: "1.4.1" 381 | term_glyph: 382 | dependency: transitive 383 | description: 384 | name: term_glyph 385 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 386 | url: "https://pub.dev" 387 | source: hosted 388 | version: "1.2.2" 389 | test_api: 390 | dependency: transitive 391 | description: 392 | name: test_api 393 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 394 | url: "https://pub.dev" 395 | source: hosted 396 | version: "0.7.4" 397 | typed_data: 398 | dependency: transitive 399 | description: 400 | name: typed_data 401 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 402 | url: "https://pub.dev" 403 | source: hosted 404 | version: "1.4.0" 405 | url_launcher: 406 | dependency: "direct main" 407 | description: 408 | name: url_launcher 409 | sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" 410 | url: "https://pub.dev" 411 | source: hosted 412 | version: "6.3.1" 413 | url_launcher_android: 414 | dependency: transitive 415 | description: 416 | name: url_launcher_android 417 | sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" 418 | url: "https://pub.dev" 419 | source: hosted 420 | version: "6.3.15" 421 | url_launcher_ios: 422 | dependency: transitive 423 | description: 424 | name: url_launcher_ios 425 | sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" 426 | url: "https://pub.dev" 427 | source: hosted 428 | version: "6.3.2" 429 | url_launcher_linux: 430 | dependency: transitive 431 | description: 432 | name: url_launcher_linux 433 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 434 | url: "https://pub.dev" 435 | source: hosted 436 | version: "3.2.1" 437 | url_launcher_macos: 438 | dependency: transitive 439 | description: 440 | name: url_launcher_macos 441 | sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" 442 | url: "https://pub.dev" 443 | source: hosted 444 | version: "3.2.2" 445 | url_launcher_platform_interface: 446 | dependency: transitive 447 | description: 448 | name: url_launcher_platform_interface 449 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 450 | url: "https://pub.dev" 451 | source: hosted 452 | version: "2.3.2" 453 | url_launcher_web: 454 | dependency: "direct main" 455 | description: 456 | name: url_launcher_web 457 | sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" 458 | url: "https://pub.dev" 459 | source: hosted 460 | version: "2.4.0" 461 | url_launcher_windows: 462 | dependency: transitive 463 | description: 464 | name: url_launcher_windows 465 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" 466 | url: "https://pub.dev" 467 | source: hosted 468 | version: "3.1.4" 469 | vector_math: 470 | dependency: transitive 471 | description: 472 | name: vector_math 473 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 474 | url: "https://pub.dev" 475 | source: hosted 476 | version: "2.1.4" 477 | vm_service: 478 | dependency: transitive 479 | description: 480 | name: vm_service 481 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" 482 | url: "https://pub.dev" 483 | source: hosted 484 | version: "14.3.1" 485 | web: 486 | dependency: "direct main" 487 | description: 488 | name: web 489 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 490 | url: "https://pub.dev" 491 | source: hosted 492 | version: "1.1.1" 493 | sdks: 494 | dart: ">=3.7.0 <4.0.0" 495 | flutter: ">=3.27.0" 496 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------