Replaces the Material 3 screens with the Noo design system: warm neutrals, one violet accent, pill controls, Schibsted Grotesk/ Instrument Sans, Lucide icons, no gradients/shadows. - New lib/widgets/noo/ component kit (core, lists, files, media, nav, overlays) plus lib/theme/design_tokens.dart for the color/type/space/ radius/motion tokens. - Rebuilt the app shell (top/bottom bars, drawer, desktop sidebar and toolbar), every tab (Files/Offline, Photos, Favorites, Recent, Activity, Trash, Shares), Settings, the lock and login screens, the media viewer, search, the details/share sheets, and the share-upload and move/copy destination pickers. - Added a design canvas (linked from DESIGN_SYSTEM.md) covering the screens the spec didn't already describe, with matching Android and iOS chrome; wrote up the approved recipes into DESIGN_SYSTEM.md §4. - Removed now-dead legacy widgets (media_grid_tile, swipeable_item, sync_status_badge, selectable_thumbnail) and updated architecture.md/styling.md/standards.md to describe the new structure and component/testing conventions. - Added widget tests for the noo/ component kit. This is a UI-only rework: no provider/model/service behavior changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
113 lines
3.9 KiB
Dart
113 lines
3.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
import '../providers/session_controller.dart';
|
|
import '../theme/design_tokens.dart';
|
|
import '../widgets/noo/nav/noo_top_bar.dart';
|
|
import '../widgets/noo/noo_layout.dart';
|
|
|
|
/// Every Login Flow v2 page - first login or an additional account -
|
|
/// rendered in Flutter's own WebView rather than a Chrome Custom Tab or
|
|
/// url_launcher's `LaunchMode.inAppWebView`. The latter hosts a bare native
|
|
/// WebView Activity with no chrome of its own: no close button, and on at
|
|
/// least some devices it draws edge-to-edge and hides the status bar with
|
|
/// no way back short of the OS back gesture. A normal Scaffold/AppBar here
|
|
/// gets the status bar/safe-area handling and a close button for free,
|
|
/// exactly like every other screen in the app - and unlike a Custom Tab,
|
|
/// this screen can close itself automatically once login succeeds.
|
|
///
|
|
/// See SessionController.startLoginFlow's doc comment for the full rationale.
|
|
class LoginWebViewView extends StatefulWidget {
|
|
final Uri url;
|
|
|
|
const LoginWebViewView({super.key, required this.url});
|
|
|
|
@override
|
|
State<LoginWebViewView> createState() => _LoginWebViewViewState();
|
|
}
|
|
|
|
class _LoginWebViewViewState extends State<LoginWebViewView> {
|
|
late final WebViewController _controller;
|
|
bool _pageLoading = true;
|
|
bool _popped = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = WebViewController()
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
..setNavigationDelegate(
|
|
NavigationDelegate(
|
|
onPageStarted: (_) {
|
|
if (mounted) setState(() => _pageLoading = true);
|
|
},
|
|
onPageFinished: (_) {
|
|
if (mounted) setState(() => _pageLoading = false);
|
|
},
|
|
),
|
|
);
|
|
// Android's WebView CookieManager is a single store shared/persisted
|
|
// across every WebView instance in the app, not scoped to this
|
|
// controller - without clearing it first, "Add Account" (or logging
|
|
// back in as a different user on the same server) would silently land
|
|
// on whichever account's Nextcloud session cookie is already there
|
|
// instead of prompting for credentials, defeating the point of
|
|
// starting a fresh Login Flow v2 at all.
|
|
WebViewCookieManager().clearCookies().then((_) {
|
|
if (mounted) _controller.loadRequest(widget.url);
|
|
});
|
|
}
|
|
|
|
void _cancel() {
|
|
context.read<SessionController>().cancelLoginFlow();
|
|
Navigator.of(context).pop();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final session = context.watch<SessionController>();
|
|
final colors = context.nooColors;
|
|
|
|
// The login flow finished (success or error) or was cancelled
|
|
// elsewhere - close this screen automatically rather than leaving it
|
|
// sitting open over a flow that's no longer awaiting the browser.
|
|
if (!_popped &&
|
|
session.loginFlowStatus != LoginFlowStatus.awaitingBrowser) {
|
|
_popped = true;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) Navigator.of(context).pop();
|
|
});
|
|
}
|
|
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (didPop, _) {
|
|
if (!didPop) _cancel();
|
|
},
|
|
child: Scaffold(
|
|
backgroundColor: colors.bg,
|
|
appBar: NooTopBar(
|
|
style: NooLayout.navStyle(context),
|
|
title: 'Log in',
|
|
leading: NooTopBarButton(
|
|
icon: LucideIcons.x,
|
|
tooltip: 'Cancel',
|
|
onTap: _cancel,
|
|
),
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
WebViewWidget(controller: _controller),
|
|
if (_pageLoading)
|
|
LinearProgressIndicator(
|
|
color: colors.accent,
|
|
backgroundColor: colors.surface3,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|