Files
noo/lib/views/login_webview_view.dart
T
ayushyaandClaude Opus 5.5 56784ef212 Rebuild UI on the Noo design system
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>
2026-09-26 01:51:58 -04:00

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,
),
],
),
),
);
}
}