improved tab handling by id
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
import 'package:uuid/data.dart';
|
||||||
|
import 'package:uuid/rng.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
final uuid = Uuid(goptions: GlobalOptions(CryptoRNG()));
|
||||||
@@ -542,7 +542,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
if (activeWebView?.key != null && webViews.length > 1) {
|
if (activeWebView?.key != null && webViews.length > 1) {
|
||||||
ref
|
ref
|
||||||
.read(webViewRepositoryProvider.notifier)
|
.read(webViewRepositoryProvider.notifier)
|
||||||
.closeTab(activeWebView!.key!);
|
.closeTab(activeWebView!.tabId);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
//Mark back as unhandled and navigator will pop
|
//Mark back as unhandled and navigator will pop
|
||||||
@@ -584,13 +584,13 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
(activeWebView != null)
|
(activeWebView != null)
|
||||||
? webViews.keys
|
? webViews.keys
|
||||||
.toList()
|
.toList()
|
||||||
.indexOf(activeWebView.page.value.key)
|
.indexOf(activeWebView.tabId)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
index: (activeWebView != null)
|
index: (activeWebView != null)
|
||||||
? webViews.keys
|
? webViews.keys
|
||||||
.toList()
|
.toList()
|
||||||
.indexOf(activeWebView.page.value.key) +
|
.indexOf(activeWebView.tabId) +
|
||||||
1
|
1
|
||||||
: 0,
|
: 0,
|
||||||
children: [const LandingContent(), ...webViews.values],
|
children: [const LandingContent(), ...webViews.values],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
|
||||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/widgets/web_view_tab.dart';
|
import 'package:lensai/features/web_view/presentation/widgets/web_view_tab.dart';
|
||||||
@@ -109,7 +110,7 @@ class ViewTabsSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
final activeTab = ref.watch(
|
final activeTab = ref.watch(
|
||||||
webViewTabControllerProvider.select(
|
webViewTabControllerProvider.select(
|
||||||
(webView) => webView?.page.value.key,
|
(webView) => webView?.tabId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ class ViewTabsSheet extends HookConsumerWidget {
|
|||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
final index =
|
final index =
|
||||||
tabs.indexWhere((webView) => webView.key == activeTab);
|
tabs.indexWhere((webView) => webView.tabId == activeTab);
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
final reversedIndex = tabs.length - 1 - index;
|
final reversedIndex = tabs.length - 1 - index;
|
||||||
@@ -160,10 +161,17 @@ class ViewTabsSheet extends HookConsumerWidget {
|
|||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
children: tabs.reversed
|
children: tabs.reversed
|
||||||
.map(
|
.map(
|
||||||
(webView) => WebViewTab(
|
(webView) => HookBuilder(
|
||||||
webView: webView,
|
key: ValueKey(webView.tabId),
|
||||||
isActive: webView.key == activeTab,
|
builder: (context) {
|
||||||
onClose: onClose,
|
final tab = useValueListenable(webView.page) as ITab;
|
||||||
|
|
||||||
|
return WebViewTab(
|
||||||
|
tab: tab,
|
||||||
|
isActive: webView.tabId == activeTab,
|
||||||
|
onClose: onClose,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
|
||||||
|
abstract interface class ITab {
|
||||||
|
String get id;
|
||||||
|
Uri get url;
|
||||||
|
String? get title;
|
||||||
|
Favicon? get favicon;
|
||||||
|
Uint8List? get screenshot;
|
||||||
|
}
|
||||||
@@ -3,25 +3,35 @@ import 'package:fast_equatable/fast_equatable.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:lensai/core/uuid.dart';
|
||||||
import 'package:lensai/domain/entities/web_page_info.dart';
|
import 'package:lensai/domain/entities/web_page_info.dart';
|
||||||
|
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
|
||||||
|
|
||||||
part 'web_view_page.g.dart';
|
part 'web_view_page.g.dart';
|
||||||
|
|
||||||
typedef PageHistory = ({bool canGoBack, bool canGoForward});
|
typedef PageHistory = ({bool canGoBack, bool canGoForward});
|
||||||
|
|
||||||
@CopyWith(constructor: '_')
|
@CopyWith(constructor: '_')
|
||||||
class WebViewPage extends WebPageInfo with FastEquatable {
|
class WebViewPage extends WebPageInfo with FastEquatable implements ITab {
|
||||||
@CopyWithField(immutable: true)
|
@CopyWithField(immutable: true)
|
||||||
final Key key;
|
final Key key;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@CopyWithField(immutable: true)
|
||||||
|
final String id;
|
||||||
|
|
||||||
final InAppWebViewController? controller;
|
final InAppWebViewController? controller;
|
||||||
|
|
||||||
// ignore: missing_field_in_equatable_props
|
// ignore: missing_field_in_equatable_props
|
||||||
final SslError? sslError;
|
final SslError? sslError;
|
||||||
|
|
||||||
|
@override
|
||||||
final Uint8List? screenshot;
|
final Uint8List? screenshot;
|
||||||
|
|
||||||
final PageHistory pageHistory;
|
final PageHistory pageHistory;
|
||||||
|
|
||||||
WebViewPage({
|
WebViewPage({
|
||||||
|
String? id,
|
||||||
this.controller,
|
this.controller,
|
||||||
required super.url,
|
required super.url,
|
||||||
this.sslError,
|
this.sslError,
|
||||||
@@ -29,10 +39,12 @@ class WebViewPage extends WebPageInfo with FastEquatable {
|
|||||||
super.favicon,
|
super.favicon,
|
||||||
this.screenshot,
|
this.screenshot,
|
||||||
this.pageHistory = (canGoBack: false, canGoForward: false),
|
this.pageHistory = (canGoBack: false, canGoForward: false),
|
||||||
}) : key = GlobalKey();
|
}) : key = GlobalKey(),
|
||||||
|
id = id ?? uuid.v7();
|
||||||
|
|
||||||
WebViewPage._({
|
WebViewPage._({
|
||||||
required this.key,
|
required this.key,
|
||||||
|
required this.id,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required super.url,
|
required super.url,
|
||||||
required this.sslError,
|
required this.sslError,
|
||||||
@@ -48,8 +60,9 @@ class WebViewPage extends WebPageInfo with FastEquatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
key,
|
key,
|
||||||
|
id,
|
||||||
controller,
|
controller,
|
||||||
url,
|
super.url,
|
||||||
sslError?.toString(),
|
sslError?.toString(),
|
||||||
title,
|
title,
|
||||||
favicon?.toString(),
|
favicon?.toString(),
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class _$WebViewPageCWProxyImpl implements _$WebViewPageCWProxy {
|
|||||||
}) {
|
}) {
|
||||||
return WebViewPage._(
|
return WebViewPage._(
|
||||||
key: _value.key,
|
key: _value.key,
|
||||||
|
id: _value.id,
|
||||||
controller: controller == const $CopyWithPlaceholder()
|
controller: controller == const $CopyWithPlaceholder()
|
||||||
? _value.controller
|
? _value.controller
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -8,35 +8,34 @@ part 'web_view.g.dart';
|
|||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class WebViewTabController extends _$WebViewTabController {
|
class WebViewTabController extends _$WebViewTabController {
|
||||||
late Map<Key, WebView> _webViewTabs;
|
late Map<String, WebView> _webViewTabs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WebView? build() {
|
WebView? build() {
|
||||||
_webViewTabs = ref.watch(webViewRepositoryProvider);
|
_webViewTabs = ref.watch(webViewRepositoryProvider);
|
||||||
return (stateOrNull != null)
|
return (stateOrNull != null)
|
||||||
? _webViewTabs[stateOrNull!.page.value.key] ??
|
? _webViewTabs[stateOrNull!.tabId] ?? _webViewTabs.values.lastOrNull
|
||||||
_webViewTabs.values.lastOrNull
|
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showTab(Key? key) {
|
void showTab(String? id) {
|
||||||
state = (key != null) ? _webViewTabs[key] : null;
|
state = (id != null) ? _webViewTabs[id] : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class WebViewRepository extends _$WebViewRepository {
|
class WebViewRepository extends _$WebViewRepository {
|
||||||
@override
|
@override
|
||||||
Map<Key, WebView> build() {
|
Map<String, WebView> build() {
|
||||||
return stateOrNull ?? {};
|
return stateOrNull ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void addTab(WebView webView) {
|
void addTab(WebView webView) {
|
||||||
state = {...state, webView.page.value.key: webView};
|
state = {...state, webView.tabId: webView};
|
||||||
}
|
}
|
||||||
|
|
||||||
void closeTab(Key key) {
|
void closeTab(String id) {
|
||||||
state = Map.of(state)..remove(key);
|
state = Map.of(state)..remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void closeAllTabs() {
|
void closeAllTabs() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'web_view.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$webViewTabControllerHash() =>
|
String _$webViewTabControllerHash() =>
|
||||||
r'45eaa10e6032b04717e8ba08b8dce8ed22764f46';
|
r'3f1d7460295c6399382881ee56c4ed7f1fd5fe03';
|
||||||
|
|
||||||
/// See also [WebViewTabController].
|
/// See also [WebViewTabController].
|
||||||
@ProviderFor(WebViewTabController)
|
@ProviderFor(WebViewTabController)
|
||||||
@@ -23,12 +23,12 @@ final webViewTabControllerProvider =
|
|||||||
);
|
);
|
||||||
|
|
||||||
typedef _$WebViewTabController = Notifier<WebView?>;
|
typedef _$WebViewTabController = Notifier<WebView?>;
|
||||||
String _$webViewRepositoryHash() => r'6732fd1b8a465073b599f64d6710a61cd605c779';
|
String _$webViewRepositoryHash() => r'dae5e1527ab4ad27f6a882fb63d8e88cd812b20b';
|
||||||
|
|
||||||
/// See also [WebViewRepository].
|
/// See also [WebViewRepository].
|
||||||
@ProviderFor(WebViewRepository)
|
@ProviderFor(WebViewRepository)
|
||||||
final webViewRepositoryProvider =
|
final webViewRepositoryProvider =
|
||||||
NotifierProvider<WebViewRepository, Map<Key, WebView>>.internal(
|
NotifierProvider<WebViewRepository, Map<String, WebView>>.internal(
|
||||||
WebViewRepository.new,
|
WebViewRepository.new,
|
||||||
name: r'webViewRepositoryProvider',
|
name: r'webViewRepositoryProvider',
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
@@ -38,6 +38,6 @@ final webViewRepositoryProvider =
|
|||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef _$WebViewRepository = Notifier<Map<Key, WebView>>;
|
typedef _$WebViewRepository = Notifier<Map<String, WebView>>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SwitchNewTabController extends _$SwitchNewTabController {
|
|||||||
.read(webViewRepositoryProvider.notifier)
|
.read(webViewRepositoryProvider.notifier)
|
||||||
.addTab(WebView(tab: newTab));
|
.addTab(WebView(tab: newTab));
|
||||||
|
|
||||||
ref.read(webViewTabControllerProvider.notifier).showTab(newTab.key);
|
ref.read(webViewTabControllerProvider.notifier).showTab(newTab.id);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'switch_new_tab.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$switchNewTabControllerHash() =>
|
String _$switchNewTabControllerHash() =>
|
||||||
r'acc20371e95db182cfc5cdc924a289ff56981692';
|
r'a5b8f7fd71c8cb6cc2023b41504e5cd467c5b063';
|
||||||
|
|
||||||
/// See also [SwitchNewTabController].
|
/// See also [SwitchNewTabController].
|
||||||
@ProviderFor(SwitchNewTabController)
|
@ProviderFor(SwitchNewTabController)
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ const _webViewSupportedSchemes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
class WebView extends StatefulHookConsumerWidget {
|
class WebView extends StatefulHookConsumerWidget {
|
||||||
|
final String tabId;
|
||||||
|
|
||||||
final ValueNotifier<WebViewPage> _pageNotifier;
|
final ValueNotifier<WebViewPage> _pageNotifier;
|
||||||
|
|
||||||
final ValueNotifier<bool?> _isReaderable;
|
final ValueNotifier<bool?> _isReaderable;
|
||||||
@@ -76,6 +78,7 @@ class WebView extends StatefulHookConsumerWidget {
|
|||||||
: _pageNotifier = ValueNotifier(tab),
|
: _pageNotifier = ValueNotifier(tab),
|
||||||
_isReaderable = ValueNotifier(null),
|
_isReaderable = ValueNotifier(null),
|
||||||
_readerableApplied = ValueNotifier(false),
|
_readerableApplied = ValueNotifier(false),
|
||||||
|
tabId = tab.id,
|
||||||
super(key: tab.key);
|
super(key: tab.key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
|
||||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/widgets/favicon.dart';
|
import 'package:lensai/features/web_view/presentation/widgets/favicon.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/widgets/web_view.dart';
|
|
||||||
|
|
||||||
class WebViewTab extends HookConsumerWidget {
|
class WebViewTab extends HookConsumerWidget {
|
||||||
final WebView webView;
|
final ITab tab;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
|
||||||
final VoidCallback onClose;
|
final VoidCallback onClose;
|
||||||
|
|
||||||
const WebViewTab({
|
const WebViewTab({
|
||||||
required this.webView,
|
required this.tab,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
super.key,
|
super.key,
|
||||||
@@ -20,10 +19,8 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final page = useValueListenable(webView.page);
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
key: UniqueKey(),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isActive ? colorScheme.primary : colorScheme.outline,
|
color: isActive ? colorScheme.primary : colorScheme.outline,
|
||||||
@@ -38,8 +35,9 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
ref.read(webViewTabControllerProvider.notifier).showTab(page.key);
|
//Close first to avoid rebuilds
|
||||||
onClose();
|
onClose();
|
||||||
|
ref.read(webViewTabControllerProvider.notifier).showTab(tab.id);
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -53,7 +51,7 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.only(left: 6.0),
|
padding: const EdgeInsets.only(left: 6.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
page.title ?? 'New Tab',
|
tab.title ?? 'New Tab',
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -64,7 +62,7 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref
|
ref
|
||||||
.read(webViewRepositoryProvider.notifier)
|
.read(webViewRepositoryProvider.notifier)
|
||||||
.closeTab(page.key);
|
.closeTab(tab.id);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
),
|
),
|
||||||
@@ -76,14 +74,15 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
width: 6.0,
|
width: 6.0,
|
||||||
),
|
),
|
||||||
FaviconImage(
|
FaviconImage(
|
||||||
webPageInfo: page,
|
url: tab.url,
|
||||||
|
favicon: tab.favicon,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 6.0,
|
width: 6.0,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
page.url.authority,
|
tab.url.authority,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -92,7 +91,7 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 6,
|
height: 6,
|
||||||
),
|
),
|
||||||
if (page.screenshot != null)
|
if (tab.screenshot != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
@@ -103,7 +102,7 @@ class WebViewTab extends HookConsumerWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Image.memory(
|
child: Image.memory(
|
||||||
fit: BoxFit.fitWidth,
|
fit: BoxFit.fitWidth,
|
||||||
page.screenshot!,
|
tab.screenshot!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user