174 lines
6.8 KiB
Dart
174 lines
6.8 KiB
Dart
/// 약속: 앱의 뿌리 위젯 (MaterialApp - 화면 언어 · 밝기 · 시스템 막대 · 끌어다 놓기 · 화면 배율) 과 제목 글 [appTitle].
|
|
library;
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/cupertino.dart' show CupertinoLocalizations, DefaultCupertinoLocalizations;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
|
|
import '../app/app_controller.dart';
|
|
import '../app/bookmarks_controller.dart';
|
|
import '../app/download_manager.dart';
|
|
import 'app_actions.dart';
|
|
import 'app_drop.dart';
|
|
import 'browser_page.dart';
|
|
import 'home_page.dart';
|
|
import 'theme.dart';
|
|
import '../l10n/tr.dart';
|
|
import '../app/i18n_controller.dart';
|
|
|
|
/// 제목 표시줄 글: "JJ_MKVMaker v1.2.3"
|
|
String appTitle = 'JJ_MKVMaker';
|
|
|
|
class JjMkvMakerApp extends StatelessWidget {
|
|
final AppController controller;
|
|
final DownloadManager? downloads;
|
|
final BookmarksController? bookmarks;
|
|
final GlobalKey<NavigatorState>? navigatorKey;
|
|
final GlobalKey<ScaffoldMessengerState>? messengerKey;
|
|
final VoidCallback? onExit;
|
|
|
|
const JjMkvMakerApp({
|
|
super.key,
|
|
required this.controller,
|
|
this.downloads,
|
|
this.bookmarks,
|
|
this.navigatorKey,
|
|
this.messengerKey,
|
|
this.onExit,
|
|
});
|
|
|
|
/// 140 · 158: Flutter 기본 글 (뒤로 · Licenses 등) 을 화면 언어로 - 앱 언어 코드 → Locale
|
|
/// 앱이 더한 언어 (기계 번역 사전) 도 Flutter 가 아는 언어면 그 언어로, 모르는 언어일 때만 영어로 (한국어가 섞이지 않게).
|
|
/// "zh-Hant" · "pt-BR" 같은 코드는 글자 체계 (4글자) · 나라 (2글자 · 숫자) 로 나눈다.
|
|
static Locale localeOf(String code) {
|
|
final parts = code.split(RegExp('[-_]'));
|
|
String? script;
|
|
String? country;
|
|
for (final s in parts.skip(1)) {
|
|
if (s.length == 4) {
|
|
script = s;
|
|
} else if (s.isNotEmpty) {
|
|
country = s;
|
|
}
|
|
}
|
|
final l = Locale.fromSubtags(languageCode: parts.first.toLowerCase(), scriptCode: script, countryCode: country?.toUpperCase());
|
|
return GlobalMaterialLocalizations.delegate.isSupported(l) ? l : const Locale('en');
|
|
}
|
|
|
|
/// Flutter 기본 글 사전들. Material 은 알고 Cupertino 는 모르는 언어 (예: ps) 에서도 Cupertino 글이 비지 않게 영어로 채운다.
|
|
static const localizationsDelegates = [...GlobalMaterialLocalizations.delegates, _CupertinoFallbackDelegate()];
|
|
|
|
/// Flutter 가 아는 모든 언어. 지금 언어를 맨 앞에 두어 글자 체계 · 나라까지 그대로 고르게 ("zh-Hant" 가 간체로 바뀌지 않게).
|
|
static List<Locale> supportedLocalesFor(Locale current) => [
|
|
current,
|
|
for (final c in kMaterialSupportedLanguages)
|
|
if (c != current.languageCode || current.scriptCode != null || current.countryCode != null) Locale(c),
|
|
];
|
|
|
|
@override
|
|
Widget build(BuildContext context) => _BrightnessHost(
|
|
c: controller,
|
|
// 화면 언어를 바꾸면 (i18n) Flutter 기본 글도 그 언어로
|
|
builder: (context) => ListenableBuilder(listenable: i18n, builder: (context, _) => _app(context)),
|
|
);
|
|
|
|
Widget _app(BuildContext context) {
|
|
final locale = localeOf(uiLanguage);
|
|
return MaterialApp(
|
|
locale: locale,
|
|
supportedLocales: supportedLocalesFor(locale),
|
|
localizationsDelegates: localizationsDelegates,
|
|
title: appTitle,
|
|
debugShowCheckedModeBanner: false,
|
|
theme: buildTheme(),
|
|
// 밝기 바꿈은 화면 전체를 다시 그려 바로 (전환 그림 없이 - JjColors 를 직접 읽는 곳과 같은 때 바뀌게)
|
|
themeAnimationDuration: Duration.zero,
|
|
navigatorKey: navigatorKey,
|
|
scaffoldMessengerKey: messengerKey,
|
|
navigatorObservers: [browserRouteObserver],
|
|
// 모든 화면에: 환경 설정 · 종료 버튼이 쓸 것 + 끌어다 놓은 동영상을 목록에 추가
|
|
builder: (context, child) => AppScope(
|
|
controller: controller,
|
|
onExit: onExit,
|
|
downloads: downloads,
|
|
bookmarks: bookmarks,
|
|
child: AppDropArea(c: controller, child: _SystemBarsArea(child: UiScaler(c: controller, child: child!)))),
|
|
home: HomePage(c: controller, downloads: downloads, onExit: onExit, bookmarks: bookmarks),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// X-plore 비교 5-1 ⑦: 환경 설정 "화면 밝기" · 기기 밝기를 따라 [JjColors] 를 바꾸고 화면 전체를 다시 그린다 (상태는 그대로)
|
|
class _BrightnessHost extends StatefulWidget {
|
|
final AppController c;
|
|
final WidgetBuilder builder;
|
|
const _BrightnessHost({required this.c, required this.builder});
|
|
@override
|
|
State<_BrightnessHost> createState() => _BrightnessHostState();
|
|
}
|
|
|
|
class _BrightnessHostState extends State<_BrightnessHost> with WidgetsBindingObserver {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
widget.c.addListener(_check);
|
|
JjColors.use(_want());
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.c.removeListener(_check);
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
super.dispose();
|
|
}
|
|
|
|
Brightness _want() =>
|
|
resolveBrightness(widget.c.settings.display.brightness, WidgetsBinding.instance.platformDispatcher.platformBrightness);
|
|
|
|
@override
|
|
void didChangePlatformBrightness() => _check();
|
|
|
|
void _check() {
|
|
if (!mounted || !JjColors.use(_want())) return;
|
|
setState(() {});
|
|
// JjColors 를 직접 읽는 화면 조각도 새 색으로 (그 조각들은 Theme 이 바뀌어도 다시 그려지지 않는다)
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final root = WidgetsBinding.instance.rootElement;
|
|
if (root != null) rebuildAllElements(root);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => widget.builder(context);
|
|
}
|
|
|
|
/// Cupertino 글이 없는 언어만 영어로 (GlobalCupertinoLocalizations 뒤에 두어 그쪽이 아는 언어는 그쪽이 먼저)
|
|
class _CupertinoFallbackDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
|
|
const _CupertinoFallbackDelegate();
|
|
|
|
@override
|
|
bool isSupported(Locale locale) => true;
|
|
|
|
@override
|
|
Future<CupertinoLocalizations> load(Locale locale) => DefaultCupertinoLocalizations.load(locale);
|
|
|
|
@override
|
|
bool shouldReload(_CupertinoFallbackDelegate old) => false;
|
|
}
|
|
|
|
/// Android: 앱 화면이 상태 표시줄 · 아래쪽 작업 표시줄 밑까지 그려지므로 (Android 15 부터 기본)
|
|
/// 위쪽 버튼 줄이 시계 · 배터리 표시와 겹치지 않게 그만큼 띄운다. 전체 화면 재생 중에는 표시줄이 숨어 여백도 없다.
|
|
class _SystemBarsArea extends StatelessWidget {
|
|
final Widget child;
|
|
const _SystemBarsArea({required this.child});
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Platform.isAndroid
|
|
? ColoredBox(color: Theme.of(context).scaffoldBackgroundColor, child: SafeArea(child: child))
|
|
: child;
|
|
}
|