Add cxyz Android project structure and initial configuration

- Introduced a new Flutter project for the cxyz Android manga/comic reader, including essential files such as pubspec.yaml, AndroidManifest.xml, and Gradle configuration.
- Added .gitignore files to exclude unnecessary files and directories for both the main project and Android-specific files.
- Implemented initial README.md with project description, features, setup instructions, and API details for ComicLand, MangaRead, and MangaTown.
- Configured analysis options and dependencies in pubspec.yaml to support Flutter development.
- Established a pnpm workspace and lockfile for dependency management.
- Included various Android resources such as icons, styles, and launch backgrounds for a complete application setup.
This commit is contained in:
2026-08-29 17:50:31 +07:00
parent 6e7f1ced35
commit a7c6058540
105 changed files with 16721 additions and 275 deletions
@@ -0,0 +1,92 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WebDavSettings {
static const _kEnabled = 'webdav_enabled';
static const _kUrl = 'webdav_url';
static const _kUsername = 'webdav_username';
static const _kPath = 'webdav_path';
static const _kLastSync = 'webdav_last_sync';
static const _kLastError = 'webdav_last_error';
static const _kLastStatus = 'webdav_last_status';
static const _passwordKey = 'webdav_password';
static const defaultPath = '/cxyz/progress.json';
static const _secure = FlutterSecureStorage();
final bool enabled;
final String url;
final String username;
final String password;
final String path;
final DateTime? lastSyncAt;
final String? lastError;
final String? lastStatus;
const WebDavSettings({
required this.enabled,
required this.url,
required this.username,
required this.password,
required this.path,
this.lastSyncAt,
this.lastError,
this.lastStatus,
});
bool get isConfigured =>
url.trim().isNotEmpty && username.trim().isNotEmpty;
static Future<WebDavSettings> load() async {
final prefs = await SharedPreferences.getInstance();
final password = await _secure.read(key: _passwordKey) ?? '';
final lastSyncRaw = prefs.getString(_kLastSync);
return WebDavSettings(
enabled: prefs.getBool(_kEnabled) ?? false,
url: prefs.getString(_kUrl) ?? '',
username: prefs.getString(_kUsername) ?? '',
password: password,
path: prefs.getString(_kPath) ?? defaultPath,
lastSyncAt:
lastSyncRaw == null ? null : DateTime.tryParse(lastSyncRaw),
lastError: prefs.getString(_kLastError),
lastStatus: prefs.getString(_kLastStatus),
);
}
static Future<void> save({
required bool enabled,
required String url,
required String username,
required String password,
required String path,
}) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_kEnabled, enabled);
await prefs.setString(_kUrl, url.trim());
await prefs.setString(_kUsername, username.trim());
await prefs.setString(
_kPath,
path.trim().isEmpty ? defaultPath : path.trim(),
);
await _secure.write(key: _passwordKey, value: password);
}
static Future<void> setSyncResult({
String? status,
String? error,
DateTime? syncedAt,
}) async {
final prefs = await SharedPreferences.getInstance();
if (status != null) await prefs.setString(_kLastStatus, status);
if (error != null) {
await prefs.setString(_kLastError, error);
} else {
await prefs.remove(_kLastError);
}
if (syncedAt != null) {
await prefs.setString(_kLastSync, syncedAt.toUtc().toIso8601String());
}
}
}