Browser-Tab-Manager/lib/screens/tab_manager_home.dart

393 lines
No EOL
12 KiB
Dart

import 'package:flutter/material.dart';
import 'dart:html' as html;
import '../models/tab_data.dart';
import '../services/browser_api_service.dart';
import '../services/extension_service.dart';
import '../widgets/item_card.dart';
import '../widgets/item_list_tile.dart';
import '../widgets/search_bar.dart' as custom;
import '../widgets/filter_chips.dart';
import '../widgets/app_bar_actions.dart';
class TabManagerHome extends StatefulWidget {
const TabManagerHome({super.key});
@override
State<TabManagerHome> createState() => _TabManagerHomeState();
}
class _TabManagerHomeState extends State<TabManagerHome> {
// State variables
List<TabData> allItems = [];
List<TabData> filteredItems = [];
final TextEditingController searchController = TextEditingController();
bool isGridView = true;
String sortBy = 'recent';
String filterType = 'all';
bool isLoading = true;
bool extensionConnected = false;
bool extensionMode = false;
// Services
final BrowserApiService _browserApi = BrowserApiService();
final ExtensionService _extensionService = ExtensionService();
@override
void initState() {
super.initState();
_setupExtensionService();
_loadAllData();
searchController.addListener(_filterItems);
}
@override
void dispose() {
searchController.dispose();
super.dispose();
}
void _setupExtensionService() {
_extensionService.onTabsUpdate = (tabs) {
setState(() {
allItems = tabs;
extensionConnected = true;
extensionMode = true;
_filterItems();
});
};
_extensionService.onTrackingStart = () {
setState(() {
extensionMode = true;
extensionConnected = true;
});
};
_extensionService.onTrackingStop = () {
setState(() {
extensionMode = false;
_loadAllData();
});
};
_extensionService.setupListener();
}
void _startExtensionTracking() {
_extensionService.startTracking();
setState(() {
extensionMode = true;
allItems.clear();
});
}
void _stopExtensionTracking() {
_extensionService.stopTracking();
setState(() {
extensionMode = false;
});
_loadAllData();
}
Future<void> _loadAllData() async {
if (extensionMode) return;
setState(() {
isLoading = true;
});
try {
final tabs = await _browserApi.getTabs();
final bookmarks = await _browserApi.getBookmarks();
final history = await _browserApi.getHistory();
setState(() {
allItems = [...tabs, ...bookmarks, ...history];
_filterItems();
isLoading = false;
});
_checkExtensionConnection();
} catch (e) {
print('Error loading data: $e');
setState(() {
isLoading = false;
});
}
}
void _checkExtensionConnection() {
_extensionService.getStatus();
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted) {
setState(() {});
}
});
}
void _filterItems() {
final query = searchController.text.toLowerCase();
setState(() {
filteredItems = allItems.where((item) {
if (filterType != 'all' && item.type != filterType.replaceAll('s', '')) {
return false;
}
if (query.isNotEmpty) {
return item.title.toLowerCase().contains(query) ||
item.url.toLowerCase().contains(query);
}
return true;
}).toList();
_sortItems();
});
}
void _sortItems() {
switch (sortBy) {
case 'recent':
filteredItems.sort((a, b) => b.lastAccessed.compareTo(a.lastAccessed));
break;
case 'title':
filteredItems.sort((a, b) => a.title.compareTo(b.title));
break;
case 'url':
filteredItems.sort((a, b) => a.url.compareTo(b.url));
break;
case 'visits':
filteredItems.sort((a, b) =>
(b.visitCount ?? 0).compareTo(a.visitCount ?? 0));
break;
}
filteredItems.sort((a, b) => b.isPinned ? 1 : (a.isPinned ? -1 : 0));
}
Future<void> _openItem(TabData item) async {
print('🔥 DEBUG: _openItem called with item: ${item.title}');
print('🔥 DEBUG: Extension mode: $extensionMode');
print('🔥 DEBUG: Item type: ${item.type}');
print('🔥 DEBUG: Item URL: ${item.url}');
if (extensionMode) {
print('🔥 DEBUG: Opening in new tab via html.window.open');
html.window.open(item.url, '_blank');
} else {
if (item.type == 'tab') {
print('🔥 DEBUG: Switching to existing tab with ID: ${item.id}');
await _browserApi.switchToTab(item.id);
} else {
print('🔥 DEBUG: Opening new tab for bookmark/history');
await _browserApi.openTab(item.url);
}
}
print('🔥 DEBUG: _openItem completed');
}
Future<void> _deleteItem(TabData item) async {
if (extensionMode) return;
if (item.type == 'tab') {
await _browserApi.closeTab(item.id);
} else if (item.type == 'bookmark') {
await _browserApi.removeBookmark(item.id);
}
await _loadAllData();
}
Future<void> _togglePin(TabData item) async {
if (extensionMode) return;
if (item.type == 'tab') {
await _browserApi.togglePinTab(item.id, !item.isPinned);
await _loadAllData();
}
}
String _getIcon(TabData item) {
if (item.favicon.isNotEmpty && !item.favicon.contains('data:')) {
return '🌐';
}
switch (item.type) {
case 'tab':
return '🔖';
case 'bookmark':
return '';
case 'history':
return '🕒';
default:
return '🌐';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
const Text('Browser Tab Manager'),
if (extensionMode) ...[
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'TRACKING',
style: TextStyle(fontSize: 10, color: Colors.white),
),
),
],
],
),
actions: [
AppBarActions(
extensionMode: extensionMode,
extensionConnected: extensionConnected,
isGridView: isGridView,
sortBy: sortBy,
onStartTracking: _startExtensionTracking,
onStopTracking: _stopExtensionTracking,
onRefresh: _loadAllData,
onToggleView: () {
setState(() {
isGridView = !isGridView;
});
},
onSortChanged: (value) {
setState(() {
sortBy = value;
_filterItems();
});
},
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
custom.SearchBar(
controller: searchController,
extensionMode: extensionMode,
onClear: () {
searchController.clear();
},
),
const SizedBox(height: 12),
if (!extensionMode)
FilterChips(
allItems: allItems,
filterType: filterType,
onFilterChanged: (type) {
setState(() {
filterType = type;
_filterItems();
});
},
),
],
),
),
Expanded(
child: isLoading
? const Center(child: CircularProgressIndicator())
: filteredItems.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
extensionMode ? Icons.track_changes : Icons.search_off,
size: 80,
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.3),
),
const SizedBox(height: 16),
Text(
extensionMode
? 'Waiting for tabs...'
: 'No items found',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
extensionMode
? 'Open some tabs to see them here'
: 'Try a different search or filter',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
)
: isGridView
? GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300,
childAspectRatio: 1.3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: filteredItems.length,
itemBuilder: (context, index) {
return ItemCard(
item: filteredItems[index],
icon: _getIcon(filteredItems[index]),
onTap: () => _openItem(filteredItems[index]),
onDelete: () => _deleteItem(filteredItems[index]),
onTogglePin: () => _togglePin(filteredItems[index]),
extensionMode: extensionMode,
);
},
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: filteredItems.length,
itemBuilder: (context, index) {
return ItemListTile(
item: filteredItems[index],
icon: _getIcon(filteredItems[index]),
onTap: () => _openItem(filteredItems[index]),
onDelete: () => _deleteItem(filteredItems[index]),
onTogglePin: () => _togglePin(filteredItems[index]),
extensionMode: extensionMode,
);
},
),
),
],
),
);
}
}
// This is the main screen of our app where users see and manage their browser tabs.
//
// It is a StatefulWidget which means it holds data that can change over time.
//
// The state includes lists of tabs, search text, view preferences, and loading status.
//
// It communicates with two services: BrowserApiService for browser data and ExtensionService for real-time tracking.
//
// When the screen loads, it fetches all tabs, bookmarks, and history from the browser.
//
// Users can search items, filter by type, sort by different criteria, and switch between grid and list views.
//
// Extension mode allows real-time tracking of browser tabs as they open and close.
//
// The build method creates the UI with an app bar, search box, filters, and either a grid or list of items.
//
// User actions like opening, deleting, or pinning items trigger methods that update the browser and refresh the display.
//
// This is the central hub that coordinates all the app functionality and user interactions.