feat: Enhance TTS player functionality and UI
- Added resume functionality to TTS player when paused. - Display voice name or language in TTS player UI. - Improved error handling in reader provider with debug messages. - Updated TTS service to configure Vietnamese voice and handle platform-specific audio settings. - Removed wakelock dependency and related code. - Fixed search screen error handling. - Updated settings screen to navigate to home after sign out. - Improved splash screen with timer management. - Enhanced main app error handling with logging. - Removed unused package_info_plus and wakelock_plus dependencies. - Added environment variable support for mobile runtime. - Integrated Google Sign-In configuration for Android. - Created logging observer for Riverpod providers. - Added scripts for environment setup and Google Sign-In validation.
This commit is contained in:
@@ -1,13 +1,29 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class AppConfig {
|
||||
AppConfig._();
|
||||
|
||||
static const String baseUrl = String.fromEnvironment(
|
||||
'BASE_URL',
|
||||
defaultValue: 'https://localhost:3000',
|
||||
);
|
||||
static const String _baseUrlFromEnv = String.fromEnvironment('BASE_URL');
|
||||
|
||||
static String get baseUrl {
|
||||
if (_baseUrlFromEnv.isNotEmpty) {
|
||||
return _baseUrlFromEnv;
|
||||
}
|
||||
|
||||
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
|
||||
return 'http://10.0.2.2:8000';
|
||||
}
|
||||
|
||||
return 'http://localhost:8000';
|
||||
}
|
||||
|
||||
static const String googleClientId = String.fromEnvironment(
|
||||
'GOOGLE_CLIENT_ID',
|
||||
defaultValue: '',
|
||||
);
|
||||
|
||||
static const String googleServerClientId = String.fromEnvironment(
|
||||
'GOOGLE_SERVER_CLIENT_ID',
|
||||
defaultValue: '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class AppProviderObserver extends ProviderObserver {
|
||||
const AppProviderObserver();
|
||||
|
||||
@override
|
||||
void providerDidFail(
|
||||
ProviderBase<Object?> provider,
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
ProviderContainer container,
|
||||
) {
|
||||
debugPrint('[APP][PROVIDER_ERROR] ${provider.name ?? provider.runtimeType}: $error');
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -41,13 +41,24 @@ class NovelModel extends Equatable {
|
||||
final SeriesModel? series;
|
||||
final LatestChapterInfo? latestChapter;
|
||||
|
||||
static String _stringValue(dynamic value, {String fallback = ''}) {
|
||||
if (value is String) return value;
|
||||
if (value == null) return fallback;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
static int _intValue(dynamic value, {int fallback = 0}) {
|
||||
if (value is num) return value.toInt();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
factory NovelModel.fromJson(Map<String, dynamic> json) => NovelModel(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
slug: json['slug'] as String,
|
||||
authorName: json['authorName'] as String,
|
||||
status: json['status'] as String,
|
||||
totalChapters: (json['totalChapters'] as num).toInt(),
|
||||
id: _stringValue(json['id']),
|
||||
title: _stringValue(json['title'], fallback: 'Không rõ tiêu đề'),
|
||||
slug: _stringValue(json['slug']),
|
||||
authorName: _stringValue(json['authorName'], fallback: 'Chưa rõ tác giả'),
|
||||
status: _stringValue(json['status'], fallback: 'Đang ra'),
|
||||
totalChapters: _intValue(json['totalChapters']),
|
||||
originalTitle: json['originalTitle'] as String?,
|
||||
description: json['description'] as String?,
|
||||
coverUrl: json['coverUrl'] as String?,
|
||||
|
||||
@@ -4,24 +4,40 @@ class ReadingSettings {
|
||||
this.lineHeight = 1.8,
|
||||
this.letterSpacing = 0,
|
||||
this.fontFamily = 'serif',
|
||||
this.themePreset = 'paper',
|
||||
this.horizontalPadding = 20,
|
||||
this.paragraphSpacing = 24,
|
||||
this.textAlign = 'justify',
|
||||
});
|
||||
|
||||
final double fontSize;
|
||||
final double lineHeight;
|
||||
final double letterSpacing;
|
||||
final String fontFamily;
|
||||
final String themePreset;
|
||||
final double horizontalPadding;
|
||||
final double paragraphSpacing;
|
||||
final String textAlign;
|
||||
|
||||
ReadingSettings copyWith({
|
||||
double? fontSize,
|
||||
double? lineHeight,
|
||||
double? letterSpacing,
|
||||
String? fontFamily,
|
||||
String? themePreset,
|
||||
double? horizontalPadding,
|
||||
double? paragraphSpacing,
|
||||
String? textAlign,
|
||||
}) =>
|
||||
ReadingSettings(
|
||||
fontSize: fontSize ?? this.fontSize,
|
||||
lineHeight: lineHeight ?? this.lineHeight,
|
||||
letterSpacing: letterSpacing ?? this.letterSpacing,
|
||||
fontFamily: fontFamily ?? this.fontFamily,
|
||||
themePreset: themePreset ?? this.themePreset,
|
||||
horizontalPadding: horizontalPadding ?? this.horizontalPadding,
|
||||
paragraphSpacing: paragraphSpacing ?? this.paragraphSpacing,
|
||||
textAlign: textAlign ?? this.textAlign,
|
||||
);
|
||||
|
||||
factory ReadingSettings.fromJson(Map<String, dynamic> json) => ReadingSettings(
|
||||
@@ -29,6 +45,10 @@ class ReadingSettings {
|
||||
lineHeight: (json['lineHeight'] as num?)?.toDouble() ?? 1.8,
|
||||
letterSpacing: (json['letterSpacing'] as num?)?.toDouble() ?? 0,
|
||||
fontFamily: json['fontFamily'] as String? ?? 'serif',
|
||||
themePreset: json['themePreset'] as String? ?? 'paper',
|
||||
horizontalPadding: (json['horizontalPadding'] as num?)?.toDouble() ?? 20,
|
||||
paragraphSpacing: (json['paragraphSpacing'] as num?)?.toDouble() ?? 24,
|
||||
textAlign: json['textAlign'] as String? ?? 'justify',
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -36,5 +56,9 @@ class ReadingSettings {
|
||||
'lineHeight': lineHeight,
|
||||
'letterSpacing': letterSpacing,
|
||||
'fontFamily': fontFamily,
|
||||
'themePreset': themePreset,
|
||||
'horizontalPadding': horizontalPadding,
|
||||
'paragraphSpacing': paragraphSpacing,
|
||||
'textAlign': textAlign,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../storage/secure_store.dart';
|
||||
|
||||
@@ -18,12 +19,28 @@ class ApiClient {
|
||||
dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
debugPrint('[API] ${options.method} ${options.baseUrl}${options.path}');
|
||||
final token = await _secureStore.getAccessToken();
|
||||
if (token != null && token.isNotEmpty) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
handler.next(options);
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
debugPrint(
|
||||
'[API][OK] ${response.requestOptions.method} '
|
||||
'${response.requestOptions.baseUrl}${response.requestOptions.path} '
|
||||
'-> ${response.statusCode}',
|
||||
);
|
||||
handler.next(response);
|
||||
},
|
||||
onError: (error, handler) {
|
||||
debugPrint(
|
||||
'[API][ERROR] ${error.requestOptions.method} ${error.requestOptions.baseUrl}${error.requestOptions.path} '
|
||||
'-> ${error.type}: ${error.message}',
|
||||
);
|
||||
handler.next(error);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ class LocalStore {
|
||||
static const _kLineHeight = 'reader_line_height';
|
||||
static const _kLetterSpacing = 'reader_letter_spacing';
|
||||
static const _kFontFamily = 'reader_font_family';
|
||||
static const _kThemePreset = 'reader_theme_preset';
|
||||
static const _kHorizontalPadding = 'reader_horizontal_padding';
|
||||
static const _kParagraphSpacing = 'reader_paragraph_spacing';
|
||||
static const _kTextAlign = 'reader_text_align';
|
||||
static const _kProgressChapterId = 'progress_chapter_id_';
|
||||
static const _kProgressChapterNum = 'progress_chapter_num_';
|
||||
static const _kProgressOffset = 'progress_offset_';
|
||||
@@ -19,6 +23,10 @@ class LocalStore {
|
||||
await prefs.setDouble(_kLineHeight, settings.lineHeight);
|
||||
await prefs.setDouble(_kLetterSpacing, settings.letterSpacing);
|
||||
await prefs.setString(_kFontFamily, settings.fontFamily);
|
||||
await prefs.setString(_kThemePreset, settings.themePreset);
|
||||
await prefs.setDouble(_kHorizontalPadding, settings.horizontalPadding);
|
||||
await prefs.setDouble(_kParagraphSpacing, settings.paragraphSpacing);
|
||||
await prefs.setString(_kTextAlign, settings.textAlign);
|
||||
}
|
||||
|
||||
Future<ReadingSettings?> loadReadingSettings() async {
|
||||
@@ -29,6 +37,10 @@ class LocalStore {
|
||||
lineHeight: prefs.getDouble(_kLineHeight) ?? 1.8,
|
||||
letterSpacing: prefs.getDouble(_kLetterSpacing) ?? 0,
|
||||
fontFamily: prefs.getString(_kFontFamily) ?? 'serif',
|
||||
themePreset: prefs.getString(_kThemePreset) ?? 'paper',
|
||||
horizontalPadding: prefs.getDouble(_kHorizontalPadding) ?? 20,
|
||||
paragraphSpacing: prefs.getDouble(_kParagraphSpacing) ?? 24,
|
||||
textAlign: prefs.getString(_kTextAlign) ?? 'justify',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,18 @@ class AppTheme {
|
||||
foregroundColor: Color(0xFF121826),
|
||||
),
|
||||
);
|
||||
|
||||
static final ThemeData darkTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFF155DFC),
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF0E1420),
|
||||
appBarTheme: const AppBarTheme(
|
||||
centerTitle: false,
|
||||
backgroundColor: Color(0xFF0E1420),
|
||||
foregroundColor: Color(0xFFE5EAF3),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user