+id: json[
'id'].
toString(),
+title: json[
'title'] ??
'Untitled',
+url: json[
'url'] ??
'',
\ No newline at end of file
diff --git a/Study/tabdata-visual-explanation.html b/Study/tabdata-visual-explanation.html
new file mode 100644
index 0000000..e49e4ac
--- /dev/null
+++ b/Study/tabdata-visual-explanation.html
@@ -0,0 +1,960 @@
+
+
+
+
+
+
TabData Visual Explanation
+
+
+
+
+
📊 TabData Class - Visual Guide
+
+
+
+
🎯 What is TabData?
+
+ TabData is a blueprint (or template) that defines how we store information about tabs, bookmarks, and history items.
+
+
+
+
+
+
id: "tab_123"
+
title: "Flutter - YouTube"
+
url: "https://youtube.com"
+
favicon: "https://youtube.com/favicon.ico"
+
lastAccessed: 2025-10-20 14:30:00
+
isPinned: true
+
type: "tab"
+
visitCount: 47
+
folder: null
+
+
+
+
+ Every tab, bookmark, or history item in your app is stored as a TabData object containing this information.
+
+
+
+
+
+
📋 The Properties (Variables)
+
These are the pieces of information each TabData object holds:
+
+
+
+ String id
+ Text
+ Unique identifier (like a social security number for the tab)
+ REQUIRED
+
+
+
+ String title
+ Text
+ The page title (e.g., "Google")
+ REQUIRED
+
+
+
+ String url
+ Text
+ Web address (e.g., "https://google.com")
+ REQUIRED
+
+
+
+ String favicon
+ Text
+ The small icon for the website (defaults to empty '')
+ OPTIONAL
+
+
+
+ DateTime lastAccessed
+ Date/Time
+ When you last opened this (defaults to now)
+ OPTIONAL
+
+
+
+ bool isPinned
+ True/False
+ Is the tab pinned? (defaults to false)
+ OPTIONAL
+
+
+
+ String type
+ Text
+ What kind: 'tab', 'bookmark', or 'history' (defaults to 'tab')
+ OPTIONAL
+
+
+
+ int? visitCount
+ Number?
+ How many times visited (can be null)
+ OPTIONAL
+
+
+
+ String? folder
+ Text?
+ Bookmark folder name (can be null)
+ OPTIONAL
+
+
+
+
+ 💡 Note: The ? after a type (like int? or String?) means the value can be null (empty/missing).
+
+
+
+
+
+
🏗️ The Constructor - Creating TabData Objects
+
The constructor is like a factory that builds TabData objects.
+
+
+
How to Create a TabData:
+
+
+
+final myTab = TabData(
+ id: 'tab_456',
+ title: 'Flutter Docs',
+ url: 'https://flutter.dev',
+ favicon: 'https://flutter.dev/icon.png',
+ isPinned: true,
+ visitCount: 23,
+);
+
+
+
+
+
+
📝 You provide data
+
+ id, title, url
+ + optional fields
+
+
+
+
➜
+
+
+
🏭 Constructor runs
+
+ Fills in defaults
+ Sets lastAccessed
+
+
+
+
➜
+
+
+
✨ TabData object created
+
+ Ready to use!
+
+
+
+
+
+ 🎯 Key Feature: The line : lastAccessed = lastAccessed ?? DateTime.now() means:
+
+ "If lastAccessed is provided, use it. Otherwise (??), use the current time (DateTime.now())."
+
+
+
+
+
+
🔄 The fromJson Factory - Converting JSON to TabData
+
+ When we get data from the browser API, it comes as JSON (a text format).
+ The fromJson method converts that JSON into a TabData object.
+
+
+
+
+
❌ JSON (from browser)
+
+{
+ "id": "tab_789",
+ "title": "GitHub",
+ "url": "https://github.com",
+ "favIconUrl": "...",
+ "lastVisitTime": "2025-10-20T..."
+}
+
+
+ This is just text - we can't use it directly!
+
+
+
+
+
✅ TabData Object (in our app)
+
+TabData {
+ id: "tab_789"
+ title: "GitHub"
+ url: "https://github.com"
+ favicon: "..."
+ lastAccessed: DateTime
+ isPinned: false
+ ...
+}
+
+
+ This is a proper object we can work with!
+
+
+
+
+
+
How fromJson Works:
+
+
+
+final jsonData = {'id': '123', 'title': 'Test', 'url': '...'};
+final tab = TabData.fromJson(jsonData);
+
+
+id: json['id'].toString()
+title: json['title'] ?? 'Untitled'
+
+
+
+
+
+
+
📅 Smart Date Handling
+
+ Different browser APIs send dates with different field names.
+ Our fromJson method is smart enough to handle all of them!
+
+
+
+
+
+lastAccessed: json['lastAccessed'] != null
+ ? DateTime.parse(json['lastAccessed'])
+ : (json['lastVisitTime'] != null
+ ? DateTime.parse(json['lastVisitTime'])
+ : (json['dateAdded'] != null
+ ? DateTime.parse(json['dateAdded'])
+ : (json['timestamp'] != null
+ ? DateTime.parse(json['timestamp'])
+ : DateTime.now())))
+
+
+
+
+ Try 1:
+ lastAccessed?
+
+
→
+
+ Try 2:
+ lastVisitTime?
+
+
→
+
+ Try 3:
+ dateAdded?
+
+
→
+
+ Try 4:
+ timestamp?
+
+
→
+
+ Fallback:
+ Use now!
+
+
+
+
+ 💡 Why? Different browsers and APIs use different names for the same thing.
+ This ensures our app works with all of them!
+
+
+
+
+
+
🛡️ The ?? Operator - Providing Defaults
+
+ The ?? operator means: "if null (missing), use this default value instead"
+
+
+
+
+
Without ?? (Would Crash)
+
+title: json['title']
+
+
+
+
+
+
With ?? (Safe)
+
+title: json['title'] ?? 'Untitled'
+
+
+
+
+
+
+
All the defaults in fromJson:
+
+
+ json['title'] ?? 'Untitled'
+
+ → If no title, use "Untitled"
+
+
+
+ json['url'] ?? ''
+
+ → If no URL, use empty string
+
+
+
+ json['favicon'] ?? json['favIconUrl'] ?? ''
+
+ → Try 'favicon', then 'favIconUrl', then empty
+
+
+
+ json['isPinned'] ?? false
+
+ → If not specified, assume not pinned
+
+
+
+ json['type'] ?? 'tab'
+
+ → If no type, assume it's a tab
+
+
+
+
+
+
+
🌍 Real-World Example
+
+
+
📱 What happens when you open a tab:
+
+
+
Step 1: Browser sends JSON data
+
+{
+ "id": "tab_999",
+ "title": "Claude AI",
+ "url": "https://claude.ai",
+ "favIconUrl": "https://claude.ai/icon.png",
+ "lastVisitTime": "2025-10-20T14:30:00",
+ "visitCount": 15
+}
+
+
+
+
⬇️
+
+
+
Step 2: fromJson converts it
+
+final tab = TabData.fromJson(browserData);
+
+
+
+
⬇️
+
+
+
Step 3: We have a usable TabData object!
+
+ tab.id → "tab_999"
+ tab.title → "Claude AI"
+ tab.url → "https://claude.ai"
+ tab.favicon → "https://claude.ai/icon.png"
+ tab.lastAccessed → DateTime(2025, 10, 20, 14, 30)
+ tab.isPinned → false (default)
+ tab.type → "tab" (default)
+ tab.visitCount → 15
+ tab.folder → null
+
+
+
+
⬇️
+
+
+
Step 4: Display in UI
+
+ Now we can show: tab.title, tab.url, tab.favicon, etc. in our card widgets!
+
+
+
+
+
+
+
+
🤔 Why Is TabData Designed This Way?
+
+
+
+
✅ Type Safety
+
Each property has a specific type (String, bool, DateTime). This prevents errors like putting a number where text should be.
+
+
+
+
🛡️ Error Handling
+
The ?? operators and multiple date field checks ensure the app doesn't crash if data is missing or in a different format.
+
+
+
+
🔄 Flexibility
+
Works with different browser APIs (Chrome, Firefox, Edge) because it handles different field names and formats.
+
+
+
+
📦 Organization
+
All tab/bookmark/history data is in one place, making it easy to work with throughout the app.
+
+
+
+
🔍 Readability
+
Instead of messy JSON strings everywhere, we have clean objects: tab.title, tab.url, etc.
+
+
+
+
⚡ Efficiency
+
Creating TabData objects is fast, and we can easily create lists of them: List<TabData>.
+
+
+
+
+
+
+
🔗 How TabData Is Used in main.dart
+
+
+
The Complete Data Flow:
+
+
+
+
1. Browser API
+
+ Returns JSON data
+ about tabs/bookmarks
+
+
+
+
➜
+
+
+
2. fromJson()
+
+ Converts JSON
+ to TabData objects
+
+
+
+
➜
+
+
+
3. State Lists
+
+ Stored in allItems[]
+ and filteredItems[]
+
+
+
+
➜
+
+
+
4. UI Display
+
+ ItemCard widgets
+ show the data
+
+
+
+
+
+
+final result = await _callBrowserAPI('getTabs');
+final List<dynamic> data = json.decode(result);
+
+
+return data.map((item) {
+ item['type'] = 'tab';
+ return TabData.fromJson(item);
+}).toList();
+
+
+setState(() {
+ allItems = [...tabs, ...bookmarks, ...history];
+});
+
+
+return ItemCard(
+ item: filteredItems[index],
+ icon: _getIcon(filteredItems[index]),
+);
+
+
+
+
+
+
+
📝 Statement-by-Statement Breakdown
+
+
+
+ class TabData {
+
+ STATEMENT: Declare a new class (blueprint) named TabData
+
+
+
+
+ String id;
+
+ STATEMENT: Declare a variable named 'id' that holds text (String)
+
+
+
+
+ int? visitCount;
+
+ STATEMENT: Declare a variable that holds a number, but can also be null (empty)
+
+
+
+
+ TabData({...})
+
+ STATEMENT: Define the constructor - how to create TabData objects
+
+
+
+
+ required this.id
+
+ STATEMENT: This parameter MUST be provided when creating a TabData
+
+
+
+
+ this.favicon = ''
+
+ STATEMENT: This parameter is optional, defaults to empty string if not provided
+
+
+
+
+ : lastAccessed = lastAccessed ?? DateTime.now()
+
+ STATEMENT: Initializer list - set lastAccessed to the provided value, or current time if null
+
+
+
+
+ factory TabData.fromJson(...)
+
+ STATEMENT: Define a factory method - an alternative way to create TabData from JSON
+
+
+
+
+ json['id'].toString()
+
+ STATEMENT: Get the 'id' field from JSON and convert it to a string
+
+
+
+
+ json['title'] ?? 'Untitled'
+
+ STATEMENT: Get 'title' from JSON, or use 'Untitled' if it's null/missing
+
+
+
+
+ DateTime.parse(json['lastAccessed'])
+
+ STATEMENT: Convert the date string from JSON into a DateTime object
+
+
+
+
+
+
+
+
⚙️ Common Operations with TabData
+
+
+
Creating a new TabData:
+
+final newTab = TabData(
+ id: 'tab_001',
+ title: 'My Website',
+ url: 'https://example.com',
+);
+
+
+
Converting JSON to TabData:
+
+final jsonData = {'id': '123', 'title': 'Test', 'url': '...'};
+final tab = TabData.fromJson(jsonData);
+
+
+
Accessing properties:
+
+print(tab.title);
+print(tab.url);
+if (tab.isPinned) {
+ print('This tab is pinned!');
+}
+
+
+
Creating a list of TabData:
+
+List<TabData> allTabs = [];
+
+
+allTabs.add(newTab);
+
+
+final tabs = jsonArray.map((item) => TabData.fromJson(item)).toList();
+
+
+
Modifying properties:
+
+tab.title = 'New Title';
+tab.isPinned = true;
+tab.lastAccessed = DateTime.now();
+
+
+
+
+
+
+
📚 Summary
+
+
+
TabData is the foundation of the entire app!
+
+
+ -
+ 🎯
+ Blueprint: Defines how we store tab/bookmark/history information
+
+ -
+ 📋
+ 9 Properties: id, title, url, favicon, lastAccessed, isPinned, type, visitCount, folder
+
+ -
+ 🏗️
+ Constructor: Creates new TabData objects with required and optional fields
+
+ -
+ 🔄
+ fromJson: Converts browser API JSON data into TabData objects
+
+ -
+ 🛡️
+ Error Handling: Uses ?? operator and multiple checks to handle missing data
+
+ -
+ 📅
+ Smart Dates: Handles 4 different date field names from different browsers
+
+ -
+ ✨
+ Clean Code: Makes working with browser data easy and type-safe
+
+
+
+
+
+
💡 Remember
+
+ Every tab, bookmark, and history item you see in the app is a TabData object.
+
+ Without TabData, we'd just have messy JSON strings everywhere!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/browser-tab-manager.tar.gz b/dist/browser-tab-manager.tar.gz
deleted file mode 100644
index 0903211..0000000
Binary files a/dist/browser-tab-manager.tar.gz and /dev/null differ
diff --git a/dist/browser-tab-manager/Dockerfile b/dist/browser-tab-manager/Dockerfile
deleted file mode 100644
index daf3f3f..0000000
--- a/dist/browser-tab-manager/Dockerfile
+++ /dev/null
@@ -1,52 +0,0 @@
-FROM debian:bullseye-slim AS builder
-
-# Install dependencies
-RUN apt-get update && apt-get install -y \
- curl \
- git \
- unzip \
- xz-utils \
- && rm -rf /var/lib/apt/lists/*
-
-# Create flutter user
-RUN useradd -m -u 1000 flutter
-
-# Switch to flutter user
-USER flutter
-
-# Install Flutter
-RUN git clone https://github.com/flutter/flutter.git /home/flutter/flutter -b stable --depth 1
-ENV PATH="/home/flutter/flutter/bin:${PATH}"
-
-# Configure Flutter
-RUN flutter config --no-analytics && \
- flutter config --enable-web && \
- flutter precache --web
-
-# Set working directory
-WORKDIR /home/flutter/app
-
-# Copy project files
-COPY --chown=flutter:flutter . .
-
-# Enable web for this project
-RUN flutter create . --platforms web
-
-# Get dependencies
-RUN flutter pub get
-
-# Build web app
-RUN flutter build web --release
-
-# Production stage
-FROM nginx:alpine
-
-# Copy built web app
-COPY --from=builder /home/flutter/app/build/web /usr/share/nginx/html
-
-# Copy nginx config
-COPY nginx.conf /etc/nginx/conf.d/default.conf
-
-EXPOSE 80
-
-CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/dist/browser-tab-manager/deploy.sh b/dist/browser-tab-manager/deploy.sh
deleted file mode 100755
index 26ad299..0000000
--- a/dist/browser-tab-manager/deploy.sh
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/bash
-# Server Deployment Script
-set -e
-
-echo "🚀 Deploying Browser Tab Manager on Server..."
-
-# Ensure we're in the right directory
-if [ ! -f "Dockerfile" ]; then
- echo "❌ Error: Dockerfile not found. Are you in the right directory?"
- exit 1
-fi
-
-# Create web/manifest.json if missing
-mkdir -p web
-if [ ! -f "web/manifest.json" ]; then
- cat > web/manifest.json << 'EOF'
-{
- "name": "Browser Tab Manager",
- "short_name": "TabManager",
- "start_url": ".",
- "display": "standalone",
- "background_color": "#0175C2",
- "theme_color": "#0175C2",
- "description": "Manage browser tabs in a grid view",
- "orientation": "portrait-primary",
- "prefer_related_applications": false
-}
-EOF
-fi
-
-echo "✅ Project structure ready"
-
-# Build the container
-echo "🔨 Building Podman container..."
-podman build -t browser-tab-manager .
-
-# Stop and remove existing container if running
-podman stop browser-tab-manager 2>/dev/null || true
-podman rm browser-tab-manager 2>/dev/null || true
-
-# Run the container on port 8081
-echo "🚢 Starting container..."
-podman run -d \
- --name browser-tab-manager \
- -p 8081:80 \
- --restart unless-stopped \
- browser-tab-manager
-
-echo "✅ Container started successfully!"
-echo ""
-echo "🌐 Your Browser Tab Manager is now running at:"
-SERVER_IP=$(hostname -I | awk '{print $1}' 2>/dev/null || echo "your-server-ip")
-echo " http://${SERVER_IP}:8081"
-echo ""
-echo "📝 Useful commands:"
-echo " View logs: podman logs -f browser-tab-manager"
-echo " Stop: podman stop browser-tab-manager"
-echo " Start: podman start browser-tab-manager"
-echo " Restart: podman restart browser-tab-manager"
-echo " Remove: podman rm -f browser-tab-manager"
diff --git a/dist/browser-tab-manager/lib/main.dart b/dist/browser-tab-manager/lib/main.dart
deleted file mode 100644
index 13cbe4f..0000000
--- a/dist/browser-tab-manager/lib/main.dart
+++ /dev/null
@@ -1,890 +0,0 @@
-import 'package:flutter/material.dart';
-import 'dart:html' as html;
-import 'dart:convert';
-import 'dart:js_util' as js_util;
-
-void main() {
- runApp(const BrowserTabManagerApp());
-}
-
-class BrowserTabManagerApp extends StatelessWidget {
- const BrowserTabManagerApp({super.key});
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Browser Tab Manager',
- theme: ThemeData(
- colorScheme: ColorScheme.fromSeed(
- seedColor: const Color(0xFF0175C2),
- brightness: Brightness.light,
- ),
- useMaterial3: true,
- ),
- darkTheme: ThemeData(
- colorScheme: ColorScheme.fromSeed(
- seedColor: const Color(0xFF0175C2),
- brightness: Brightness.dark,
- ),
- useMaterial3: true,
- ),
- home: const TabManagerHome(),
- );
- }
-}
-
-class TabData {
- String id;
- String title;
- String url;
- String favicon;
- DateTime lastAccessed;
- bool isPinned;
- String type; // 'tab', 'bookmark', 'history'
- int? visitCount;
- String? folder;
-
- TabData({
- required this.id,
- required this.title,
- required this.url,
- this.favicon = '',
- DateTime? lastAccessed,
- this.isPinned = false,
- this.type = 'tab',
- this.visitCount,
- this.folder,
- }) : lastAccessed = lastAccessed ?? DateTime.now();
-
- factory TabData.fromJson(Map
json) => TabData(
- id: json['id'].toString(),
- title: json['title'] ?? 'Untitled',
- url: json['url'] ?? '',
- favicon: json['favicon'] ?? json['favIconUrl'] ?? '',
- lastAccessed: json['lastAccessed'] != null
- ? DateTime.parse(json['lastAccessed'])
- : (json['lastVisitTime'] != null
- ? DateTime.parse(json['lastVisitTime'])
- : (json['dateAdded'] != null
- ? DateTime.parse(json['dateAdded'])
- : (json['timestamp'] != null
- ? DateTime.parse(json['timestamp'])
- : DateTime.now()))),
- isPinned: json['isPinned'] ?? false,
- type: json['type'] ?? 'tab',
- visitCount: json['visitCount'],
- folder: json['folder'],
- );
-}
-
-class TabManagerHome extends StatefulWidget {
- const TabManagerHome({super.key});
-
- @override
- State createState() => _TabManagerHomeState();
-}
-
-class _TabManagerHomeState extends State {
- List allItems = [];
- List filteredItems = [];
- final TextEditingController searchController = TextEditingController();
- bool isGridView = true;
- String sortBy = 'recent';
- String filterType = 'all'; // 'all', 'tabs', 'bookmarks', 'history'
- bool isLoading = true;
- bool extensionConnected = false;
- bool extensionMode = false;
-
- @override
- void initState() {
- super.initState();
- _setupExtensionListener();
- _loadAllData();
- searchController.addListener(_filterItems);
- }
-
- @override
- void dispose() {
- searchController.dispose();
- super.dispose();
- }
-
- // Setup extension listener
- void _setupExtensionListener() {
- html.window.onMessage.listen((event) {
- final data = event.data;
-
-if (data is Map && data['source'] == 'tab-tracker-extension') {
- _handleExtensionMessage(Map.from(data));
-}
- });
- }
-
- // Handle messages from extension
- void _handleExtensionMessage(Map data) {
- print('Received from extension: $data');
-
- if (data['action'] == 'updateTabs') {
- setState(() {
- final extensionTabs = (data['tabs'] as List).map((tab) {
- tab['type'] = 'tab';
- return TabData.fromJson(tab);
- }).toList();
-
- allItems = extensionTabs;
- extensionConnected = true;
- extensionMode = true;
- _filterItems();
- });
- } else if (data['action'] == 'clear') {
- if (extensionMode) {
- setState(() {
- extensionMode = false;
- _loadAllData();
- });
- }
- } else if (data['response'] != null) {
- final response = data['response'];
- if (response['status'] == 'started') {
- setState(() {
- extensionMode = true;
- extensionConnected = true;
- });
- } else if (response['status'] == 'stopped') {
- setState(() {
- extensionMode = false;
- _loadAllData();
- });
- }
- }
- }
-
- // Send message to extension
- void _sendToExtension(Map message) {
- html.window.postMessage({
- 'source': 'tab-tracker-webapp',
- ...message
- }, '*');
- }
-
- // Start extension tracking
- void _startExtensionTracking() {
- _sendToExtension({'action': 'startTracking'});
- setState(() {
- extensionMode = true;
- allItems.clear();
- });
- }
-
- // Stop extension tracking
- void _stopExtensionTracking() {
- _sendToExtension({'action': 'stopTracking'});
- setState(() {
- extensionMode = false;
- });
- _loadAllData();
- }
-
- Future _loadAllData() async {
- if (extensionMode) return; // Don't load if in extension mode
-
- setState(() {
- isLoading = true;
- });
-
- try {
- final tabs = await _getTabs();
- final bookmarks = await _getBookmarks();
- final history = await _getHistory();
-
- setState(() {
- allItems = [...tabs, ...bookmarks, ...history];
- _filterItems();
- isLoading = false;
- });
-
- // Check if extension is available
- _checkExtensionConnection();
- } catch (e) {
- print('Error loading data: $e');
- setState(() {
- isLoading = false;
- });
- }
- }
-
- void _checkExtensionConnection() {
- _sendToExtension({'action': 'getStatus'});
-
- Future.delayed(const Duration(milliseconds: 500), () {
- if (mounted) {
- setState(() {
- // extensionConnected will be set by message handler if extension responds
- });
- }
- });
- }
-
- Future> _getTabs() async {
- try {
- final result = await _callBrowserAPI('getTabs');
- if (result != null) {
- final List data = json.decode(result);
- return data.map((item) {
- item['type'] = 'tab';
- return TabData.fromJson(item);
- }).toList();
- }
- } catch (e) {
- print('Error getting tabs: $e');
- }
- return [];
- }
-
- Future> _getBookmarks() async {
- try {
- final result = await _callBrowserAPI('getBookmarks');
- if (result != null) {
- final List data = json.decode(result);
- return data.map((item) {
- item['type'] = 'bookmark';
- return TabData.fromJson(item);
- }).toList();
- }
- } catch (e) {
- print('Error getting bookmarks: $e');
- }
- return [];
- }
-
- Future> _getHistory() async {
- try {
- final result = await _callBrowserAPI('getHistory', [100]);
- if (result != null) {
- final List data = json.decode(result);
- return data.map((item) {
- item['type'] = 'history';
- return TabData.fromJson(item);
- }).toList();
- }
- } catch (e) {
- print('Error getting history: $e');
- }
- return [];
- }
-
- Future _callBrowserAPI(String method, [List? args]) async {
- try {
- // Check if BrowserAPI exists
- if (!js_util.hasProperty(html.window, 'BrowserAPI')) {
- print('BrowserAPI not found - running in development mode');
- return null;
- }
-
- final browserAPI = js_util.getProperty(html.window, 'BrowserAPI');
- final function = js_util.getProperty(browserAPI, method);
-
- final result = args == null
- ? await js_util.promiseToFuture(js_util.callMethod(function, 'call', [browserAPI]))
- : await js_util.promiseToFuture(js_util.callMethod(function, 'call', [browserAPI, ...args]));
-
- return json.encode(result);
- } catch (e) {
- print('Error calling $method: $e');
- return null;
- }
- }
-
- void _filterItems() {
- final query = searchController.text.toLowerCase();
- setState(() {
- filteredItems = allItems.where((item) {
- // Filter by type
- if (filterType != 'all' && item.type != filterType.replaceAll('s', '')) {
- return false;
- }
- // Filter by search query
- 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;
- }
- // Keep pinned tabs at the top
- filteredItems.sort((a, b) => b.isPinned ? 1 : (a.isPinned ? -1 : 0));
- }
-
- Future _openItem(TabData item) async {
- if (extensionMode) {
- // In extension mode, just open URL in new tab
- html.window.open(item.url, '_blank');
- } else {
- if (item.type == 'tab') {
- await _callBrowserAPI('switchToTab', [item.id]);
- } else {
- await _callBrowserAPI('openTab', [item.url]);
- }
- }
- }
-
- Future _deleteItem(TabData item) async {
- if (extensionMode) return; // Can't delete in extension mode
-
- if (item.type == 'tab') {
- await _callBrowserAPI('closeTab', [item.id]);
- } else if (item.type == 'bookmark') {
- await _callBrowserAPI('removeBookmark', [item.id]);
- }
- await _loadAllData();
- }
-
- Future _togglePin(TabData item) async {
- if (extensionMode) return; // Can't pin in extension mode
-
- if (item.type == 'tab') {
- await _callBrowserAPI('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) {
- final stats = {
- 'tabs': allItems.where((i) => i.type == 'tab').length,
- 'bookmarks': allItems.where((i) => i.type == 'bookmark').length,
- 'history': allItems.where((i) => i.type == 'history').length,
- };
-
- 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: [
- // Extension control button
- if (extensionMode)
- TextButton.icon(
- onPressed: _stopExtensionTracking,
- icon: const Icon(Icons.stop, color: Colors.white),
- label: const Text('Stop', style: TextStyle(color: Colors.white)),
- )
- else
- TextButton.icon(
- onPressed: extensionConnected ? _startExtensionTracking : null,
- icon: const Icon(Icons.play_arrow, color: Colors.white),
- label: const Text('Track Tabs', style: TextStyle(color: Colors.white)),
- ),
- const SizedBox(width: 8),
- IconButton(
- icon: const Icon(Icons.refresh),
- onPressed: extensionMode ? null : _loadAllData,
- tooltip: 'Refresh',
- ),
- IconButton(
- icon: Icon(isGridView ? Icons.view_list : Icons.grid_view),
- onPressed: () {
- setState(() {
- isGridView = !isGridView;
- });
- },
- tooltip: isGridView ? 'List View' : 'Grid View',
- ),
- PopupMenuButton(
- icon: const Icon(Icons.sort),
- tooltip: 'Sort by',
- onSelected: (value) {
- setState(() {
- sortBy = value;
- _filterItems();
- });
- },
- itemBuilder: (context) => [
- const PopupMenuItem(value: 'recent', child: Text('Recent')),
- const PopupMenuItem(value: 'title', child: Text('Title')),
- const PopupMenuItem(value: 'url', child: Text('URL')),
- const PopupMenuItem(value: 'visits', child: Text('Most Visited')),
- ],
- ),
- ],
- ),
- body: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: Column(
- children: [
- TextField(
- controller: searchController,
- decoration: InputDecoration(
- hintText: extensionMode
- ? 'Search tracked tabs...'
- : 'Search tabs, bookmarks, and history...',
- prefixIcon: const Icon(Icons.search),
- suffixIcon: searchController.text.isNotEmpty
- ? IconButton(
- icon: const Icon(Icons.clear),
- onPressed: () {
- searchController.clear();
- },
- )
- : null,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- ),
- ),
- ),
- const SizedBox(height: 12),
- if (!extensionMode)
- SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- child: Row(
- children: [
- FilterChip(
- label: Text('All (${allItems.length})'),
- selected: filterType == 'all',
- onSelected: (selected) {
- setState(() {
- filterType = 'all';
- _filterItems();
- });
- },
- ),
- const SizedBox(width: 8),
- FilterChip(
- label: Text('Tabs (${stats['tabs']})'),
- selected: filterType == 'tabs',
- onSelected: (selected) {
- setState(() {
- filterType = 'tabs';
- _filterItems();
- });
- },
- ),
- const SizedBox(width: 8),
- FilterChip(
- label: Text('Bookmarks (${stats['bookmarks']})'),
- selected: filterType == 'bookmarks',
- onSelected: (selected) {
- setState(() {
- filterType = 'bookmarks';
- _filterItems();
- });
- },
- ),
- const SizedBox(width: 8),
- FilterChip(
- label: Text('History (${stats['history']})'),
- selected: filterType == 'history',
- onSelected: (selected) {
- setState(() {
- filterType = 'history';
- _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,
- );
- },
- ),
- ),
- ],
- ),
- );
- }
-}
-
-class ItemCard extends StatelessWidget {
- final TabData item;
- final String icon;
- final VoidCallback onTap;
- final VoidCallback onDelete;
- final VoidCallback onTogglePin;
- final bool extensionMode;
-
- const ItemCard({
- super.key,
- required this.item,
- required this.icon,
- required this.onTap,
- required this.onDelete,
- required this.onTogglePin,
- this.extensionMode = false,
- });
-
- @override
- Widget build(BuildContext context) {
- return Card(
- clipBehavior: Clip.antiAlias,
- child: InkWell(
- onTap: onTap,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- height: 60,
- color: Theme.of(context).colorScheme.primaryContainer,
- child: Center(
- child: item.favicon.isNotEmpty && item.favicon.startsWith('http')
- ? Image.network(
- item.favicon,
- width: 32,
- height: 32,
- errorBuilder: (context, error, stackTrace) {
- return Text(icon, style: const TextStyle(fontSize: 32));
- },
- )
- : Text(icon, style: const TextStyle(fontSize: 32)),
- ),
- ),
- Expanded(
- child: Padding(
- padding: const EdgeInsets.all(12.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- if (item.isPinned)
- Icon(
- Icons.push_pin,
- size: 14,
- color: Theme.of(context).colorScheme.primary,
- ),
- if (item.isPinned) const SizedBox(width: 4),
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 6,
- vertical: 2,
- ),
- decoration: BoxDecoration(
- color: _getTypeColor(context),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- item.type.toUpperCase(),
- style: TextStyle(
- fontSize: 10,
- color: Theme.of(context).colorScheme.onPrimary,
- ),
- ),
- ),
- ],
- ),
- const SizedBox(height: 6),
- Expanded(
- child: Text(
- item.title,
- style: Theme.of(context).textTheme.titleSmall,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- const SizedBox(height: 4),
- Text(
- item.url,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- color: Theme.of(context).colorScheme.secondary,
- ),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- if (item.visitCount != null) ...[
- const SizedBox(height: 4),
- Text(
- '${item.visitCount} visits',
- style: Theme.of(context).textTheme.bodySmall,
- ),
- ],
- ],
- ),
- ),
- ),
- if (!extensionMode)
- ButtonBar(
- alignment: MainAxisAlignment.end,
- buttonPadding: EdgeInsets.zero,
- children: [
- if (item.type == 'tab')
- IconButton(
- icon: Icon(
- item.isPinned ? Icons.push_pin : Icons.push_pin_outlined,
- size: 18,
- ),
- onPressed: onTogglePin,
- tooltip: item.isPinned ? 'Unpin' : 'Pin',
- ),
- if (item.type != 'history')
- IconButton(
- icon: const Icon(Icons.delete_outline, size: 18),
- onPressed: onDelete,
- tooltip: 'Delete',
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
-
- Color _getTypeColor(BuildContext context) {
- switch (item.type) {
- case 'tab':
- return Colors.blue;
- case 'bookmark':
- return Colors.orange;
- case 'history':
- return Colors.purple;
- default:
- return Theme.of(context).colorScheme.primary;
- }
- }
-}
-
-class ItemListTile extends StatelessWidget {
- final TabData item;
- final String icon;
- final VoidCallback onTap;
- final VoidCallback onDelete;
- final VoidCallback onTogglePin;
- final bool extensionMode;
-
- const ItemListTile({
- super.key,
- required this.item,
- required this.icon,
- required this.onTap,
- required this.onDelete,
- required this.onTogglePin,
- this.extensionMode = false,
- });
-
- @override
- Widget build(BuildContext context) {
- return Card(
- margin: const EdgeInsets.only(bottom: 8),
- child: ListTile(
- leading: CircleAvatar(
- child: item.favicon.isNotEmpty && item.favicon.startsWith('http')
- ? Image.network(
- item.favicon,
- width: 20,
- height: 20,
- errorBuilder: (context, error, stackTrace) {
- return Text(icon, style: const TextStyle(fontSize: 20));
- },
- )
- : Text(icon, style: const TextStyle(fontSize: 20)),
- ),
- title: Row(
- children: [
- if (item.isPinned) ...[
- Icon(
- Icons.push_pin,
- size: 14,
- color: Theme.of(context).colorScheme.primary,
- ),
- const SizedBox(width: 4),
- ],
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: _getTypeColor(context),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- item.type.toUpperCase(),
- style: TextStyle(
- fontSize: 10,
- color: Theme.of(context).colorScheme.onPrimary,
- ),
- ),
- ),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- item.title,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ],
- ),
- subtitle: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- item.url,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- if (item.visitCount != null)
- Text('${item.visitCount} visits',
- style: Theme.of(context).textTheme.bodySmall),
- ],
- ),
- trailing: extensionMode ? null : Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (item.type == 'tab')
- IconButton(
- icon: Icon(
- item.isPinned ? Icons.push_pin : Icons.push_pin_outlined,
- ),
- onPressed: onTogglePin,
- tooltip: item.isPinned ? 'Unpin' : 'Pin',
- ),
- if (item.type != 'history')
- IconButton(
- icon: const Icon(Icons.delete_outline),
- onPressed: onDelete,
- tooltip: 'Delete',
- ),
- ],
- ),
- onTap: onTap,
- ),
- );
- }
-
- Color _getTypeColor(BuildContext context) {
- switch (item.type) {
- case 'tab':
- return Colors.blue;
- case 'bookmark':
- return Colors.orange;
- case 'history':
- return Colors.purple;
- default:
- return Theme.of(context).colorScheme.primary;
- }
- }
-}
\ No newline at end of file
diff --git a/dist/browser-tab-manager/nginx.conf b/dist/browser-tab-manager/nginx.conf
deleted file mode 100644
index f3cd537..0000000
--- a/dist/browser-tab-manager/nginx.conf
+++ /dev/null
@@ -1,20 +0,0 @@
-server {
- listen 80;
- server_name _;
- root /usr/share/nginx/html;
- index index.html;
-
- # Enable gzip
- gzip on;
- gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
-
- location / {
- try_files $uri $uri/ /index.html;
- }
-
- # Cache static assets
- location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
- expires 1y;
- add_header Cache-Control "public, immutable";
- }
-}
\ No newline at end of file
diff --git a/dist/browser-tab-manager/pubspec.yaml b/dist/browser-tab-manager/pubspec.yaml
deleted file mode 100644
index de94260..0000000
--- a/dist/browser-tab-manager/pubspec.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: browser_tab_manager
-description: A Flutter web app for managing browser tabs, bookmarks, and history
-publish_to: 'none'
-version: 1.0.0+1
-
-environment:
- sdk: '>=3.0.0 <4.0.0'
-
-dependencies:
- flutter:
- sdk: flutter
- web: ^1.1.0
-
-dev_dependencies:
- flutter_test:
- sdk: flutter
- flutter_lints: ^3.0.0
-
-flutter:
- uses-material-design: true
\ No newline at end of file
diff --git a/dist/browser-tab-manager/web/manifest.json b/dist/browser-tab-manager/web/manifest.json
deleted file mode 100644
index 8059dd7..0000000
--- a/dist/browser-tab-manager/web/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "Browser Tab Manager",
- "short_name": "TabManager",
- "start_url": ".",
- "display": "standalone",
- "background_color": "#0175C2",
- "theme_color": "#0175C2",
- "description": "Manage browser tabs in a grid view",
- "orientation": "portrait-primary",
- "prefer_related_applications": false
-}
diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart
index 07806d7..5e33698 100644
--- a/lib/constants/app_constants.dart
+++ b/lib/constants/app_constants.dart
@@ -50,9 +50,7 @@ class AppConstants {
}
-// This is our centralized configuration file where all fixed values are stored in one place.
-//
-// It contains settings used throughout the app.
+// centralized configuration fixed values stored one place.
//
// Instead of hardcoding values
//
@@ -63,5 +61,4 @@ class AppConstants {
// All user-facing messages are stored here making translation and text updates simple.
//
// Grid layout values are defined here so we can adjust the card view globally.
-//
-// This approach keeps our code clean, consistent, and easy to maintain.
\ No newline at end of file
+// Easy maintain
\ No newline at end of file
diff --git a/lib/models/tab_data.dart b/lib/models/tab_data.dart
index 7506376..ca352b7 100644
--- a/lib/models/tab_data.dart
+++ b/lib/models/tab_data.dart
@@ -1,4 +1,14 @@
+
+
+// Class creating objects "Class Declaration" The Blueprint
+// ? means can be null or nothing "Operators"
+// ?? means if null, use this instead
+
class TabData {
+
+ // FIELDS for storing data "Fields"
+ // What we will be uding in other parts of the code
+
String id;
String title;
String url;
@@ -7,9 +17,12 @@ class TabData {
bool isPinned;
String type;
int? visitCount;
- String? folder;
+ String? folder;
+ // Constructor Declaration "Constructor Declaration" The Assembly Line
TabData({
+ // Parameters saved from the data coming in
+ // "this." current object
required this.id,
required this.title,
required this.url,
@@ -19,9 +32,16 @@ class TabData {
this.type = 'tab',
this.visitCount,
this.folder,
- }) : lastAccessed = lastAccessed ?? DateTime.now();
+ })
+ // initializer logic, AFTER parameters, BEFORE Constructor
+ // ?? IF left is null, use right
+ // left side is field, right side is parameter the new saved value
+ : lastAccessed = lastAccessed ?? DateTime.now();
+ // factory logic before constructor, from json so we don't have to extract later.
factory TabData.fromJson(Map json) => TabData(
+ // Extract data pass to regular constructor
+
id: json['id'].toString(),
title: json['title'] ?? 'Untitled',
url: json['url'] ?? '',
@@ -42,22 +62,20 @@ class TabData {
);
}
-// This is our data model that represents a single tab, bookmark, or history item.
+// Data model single tab, bookmark, or history item.
//
-// It holds all the information we need about each item: title, URL, favicon, when it was accessed, etc.
+// Blueprint storing browser item information.
//
-// Think of it as a blueprint or template for storing browser item information.
+// Every tab ,bookmark is stored as a TabData object.
//
-// Every tab, bookmark, or history entry in our app is stored as a TabData object.
+// Constructor creates new TabData objects.
//
-// The constructor creates new TabData objects with required fields like id, title, and url.
+// Favicon defaults to empty string
+//
+// isPinned defaults to false.
//
-// Optional fields have default values, like favicon defaults to empty string and isPinned defaults to false.
+// FromJson factory data browser API into TabData object.
//
-// The fromJson factory method converts JSON data from the browser API into a TabData object.
+// Missing data, default values ??.
//
-// It handles different date field names from different sources like lastAccessed, lastVisitTime, dateAdded, or timestamp.
-//
-// It also handles missing data gracefully by using default values with the ?? operator.
-//
-// This class is the foundation of our app since everything revolves around displaying and managing these items.
\ No newline at end of file
+// Foundation: managing/storing tabs.
\ No newline at end of file
diff --git a/lib/screens/tab_manager_home.dart b/lib/screens/tab_manager_home.dart
index 9ee4aeb..5d92692 100644
--- a/lib/screens/tab_manager_home.dart
+++ b/lib/screens/tab_manager_home.dart
@@ -9,6 +9,9 @@ import '../widgets/search_bar.dart' as custom;
import '../widgets/filter_chips.dart';
import '../widgets/app_bar_actions.dart';
+
+// StatefulWidget
+
class TabManagerHome extends StatefulWidget {
const TabManagerHome({super.key});
diff --git a/lib/services/browser_api_service.dart b/lib/services/browser_api_service.dart
index 5eed489..a145a33 100644
--- a/lib/services/browser_api_service.dart
+++ b/lib/services/browser_api_service.dart
@@ -3,6 +3,17 @@ import 'dart:convert';
import 'dart:js_util' as js_util;
import '../models/tab_data.dart';
+
+// communication with the browser API
+// list of TabData objects
+// results LATER
+// API takes time so future
+// ASYNC WAIT
+// return empty if fail
+// final cannot be changed
+
+
+
class BrowserApiService {
Future> getTabs() async {
try {
@@ -94,20 +105,18 @@ class BrowserApiService {
}
}
-// This service acts as a bridge between our Flutter app and the browser extension.
+// Bridge Flutter app and the browser extension.
//
-// It provides clean methods to interact with browser APIs for tabs, bookmarks, and history.
+// Methods fetching from browser, convert to TabData objects.
//
-// The getTabs, getBookmarks, and getHistory methods fetch data from the browser and convert it to TabData objects.
+// SwitchToTab, openTab, closeTab allow control of tabs programmatically.
//
-// Action methods like switchToTab, openTab, closeTab allow us to control browser tabs programmatically.
+// _callBrowserAPI low level JavaScript communication.
//
-// The private _callBrowserAPI method handles the low-level JavaScript communication.
+// dart:js_util call JavaScript functions exposed by browser extension through window.BrowserAPI.
//
-// It uses dart:js_util to call JavaScript functions exposed by the browser extension through window.BrowserAPI.
+// Empty results if the API is unavailable.
//
-// All methods handle errors gracefully and return empty results if the API is unavailable.
+// Browser specific code separate from UI logic, app easier to maintain.
//
-// This abstraction keeps browser-specific code separate from our UI logic making the app easier to maintain.
-//
-// When running in development without the extension, it prints debug messages instead of crashing.
\ No newline at end of file
+// Without extension, prints debug messages instead of crashing.
\ No newline at end of file
diff --git a/test/widget_test.dart b/test/widget_test.dart
index 00d4b0f..52332c4 100644
--- a/test/widget_test.dart
+++ b/test/widget_test.dart
@@ -1,30 +1,17 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility in the flutter_test package. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:browser_tab_manager/main.dart';
void main() {
- testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ testWidgets('App loads successfully', (WidgetTester tester) async {
// Build our app and trigger a frame.
- await tester.pumpWidget(const MyApp());
+ await tester.pumpWidget(const BrowserTabManagerApp());
- // Verify that our counter starts at 0.
- expect(find.text('0'), findsOneWidget);
- expect(find.text('1'), findsNothing);
-
- // Tap the '+' icon and trigger a frame.
- await tester.tap(find.byIcon(Icons.add));
- await tester.pump();
-
- // Verify that our counter has incremented.
- expect(find.text('0'), findsNothing);
- expect(find.text('1'), findsOneWidget);
+ // Verify that the app bar title is present.
+ expect(find.text('Browser Tab Manager'), findsOneWidget);
+
+ // Verify that the search field is present.
+ expect(find.byType(TextField), findsOneWidget);
});
-}
+}
\ No newline at end of file