Initial commit: Fabled Android app

Flutter Android client for FabledAssistant with:
- Session-cookie auth via persistent cookie jar (Dio + cookie_jar)
- OAuth/SSO login via in-app WebView (flutter_inappwebview)
- Notes: list, detail (markdown render), create/edit
- Tasks: list with status tabs, create/edit with priority
- Chat: SSE streaming bubbles, conversation management
- Quick Capture FAB for rapid note/task creation
- Settings screen (change server URL, logout)
- Android home screen widget → opens chat
- Riverpod state management, go_router navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 21:28:53 -05:00
commit 4da36aa31d
64 changed files with 4053 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import '../../core/exceptions.dart';
PersistCookieJar buildCookieJar(String cookiesPath) =>
PersistCookieJar(storage: FileStorage('$cookiesPath/.cookies/'));
Dio buildDio(String baseUrl, PersistCookieJar cookieJar) {
final dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {'Content-Type': 'application/json'},
));
dio.interceptors.add(CookieManager(cookieJar));
dio.interceptors.add(_ErrorInterceptor());
return dio;
}
class _ErrorInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 401) {
handler.reject(DioException(
requestOptions: err.requestOptions,
error: const AuthException('Session expired. Please log in again.'),
type: err.type,
response: err.response,
));
return;
}
if (err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.connectionError) {
handler.reject(DioException(
requestOptions: err.requestOptions,
error: const NetworkException('Cannot reach server. Check your connection.'),
type: err.type,
response: err.response,
));
return;
}
handler.next(err);
}
}
AppException dioToApp(DioException e) {
if (e.error is AppException) return e.error as AppException;
final status = e.response?.statusCode;
if (status == 401) return const AuthException('Not authenticated.');
if (status == 404) return const NotFoundException('Resource not found.');
return NetworkException(e.message ?? 'Unknown network error.');
}