this is the UI code of SOoooooogle for mobile app



import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:http/http.dart' as http;
import 'package:shandynotes/widgets/appbarWidgets.dart';
import 'package:url_launcher/url_launcher.dart';

import '../widgets/navigationDrawer.dart';

class SearchPage extends StatefulWidget {
@override
_SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
final TextEditingController _searchController = TextEditingController();
final ScrollController _scrollController = ScrollController();
List<PDFResult> _results = [];
bool _isLoading = false;
String _errorMessage = '';
int _currentPage = 1;
bool _hasMoreResults = false;
final FocusNode _searchFocusNode = FocusNode();

// Replace with your own Google API Key and Search Engine ID
final String apiKey = 'AIzaSyCFV2xooZdAOhlOcW8ayc0luSaDglE6FqI';
final String searchEngineId = 'c7d8559bc201a420b';

Future<void> _performSearch(String query, {int startIndex = 1}) async {
if (query.isEmpty) return;

setState(() {
if (startIndex == 1) {
_results = [];
}
_isLoading = true;
_errorMessage = '';
});

try {
final url = Uri.parse('https://www.googleapis.com/customsearch/v1?'
'key=$apiKey&'
'cx=$searchEngineId&'
'q=${Uri.encodeComponent(query)}&'
'fileType=pdf&'
'start=$startIndex');

final response = await http.get(url);

if (response.statusCode == 200) {
final data = jsonDecode(response.body);

if (data['items'] != null) {
final List<PDFResult> newResults = [];

for (var item in data['items']) {
newResults.add(PDFResult(
title: item['title'] ?? 'Unknown Title',
description: item['snippet'] ?? 'No description available',
url: item['link'] ?? '',
displayUrl: item['formattedUrl'] ?? item['displayLink'] ?? '',
fileSize: _extractFileSize(item),
lastModified: _extractLastModified(item),
));
}

setState(() {
if (startIndex == 1) {
_results = newResults;
} else {
_results.addAll(newResults);
}
_hasMoreResults = data['queries'].containsKey('nextPage');
_currentPage = (startIndex / 10).ceil();
});
} else {
setState(() {
if (startIndex == 1) {
_errorMessage = 'No results found';
} else {
_hasMoreResults = false;
}
});
}
} else {
setState(() {
_errorMessage =
'Error ${response.statusCode}: ${response.reasonPhrase}';
});
}
} catch (e) {
setState(() {
_errorMessage = 'Error: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}

String _extractFileSize(Map<String, dynamic> item) {
try {
if (item['pagemap'] != null &&
item['pagemap']['metatags'] != null &&
item['pagemap']['metatags'].isNotEmpty) {
final size = item['pagemap']['metatags'][0]['contentLength'] ??
item['pagemap']['metatags'][0]['content-length'];
if (size != null) {
final sizeInt = int.tryParse(size);
if (sizeInt != null) {
if (sizeInt < 1024) return '$sizeInt B';
if (sizeInt < 1024 * 1024)
return '${(sizeInt / 1024).toStringAsFixed(1)} KB';
return '${(sizeInt / (1024 * 1024)).toStringAsFixed(1)} MB';
}
return size;
}
}
} catch (e) {
// If there's any error, just return empty string
}
return '';
}

String _extractLastModified(Map<String, dynamic> item) {
try {
if (item['pagemap'] != null &&
item['pagemap']['metatags'] != null &&
item['pagemap']['metatags'].isNotEmpty) {
return item['pagemap']['metatags'][0]['last-modified'] ??
item['pagemap']['metatags'][0]['lastModified'] ??
'';
}
} catch (e) {
// If there's any error, just return empty string
}
return '';
}

void _loadMoreResults() {
if (!_isLoading && _hasMoreResults) {
final nextStartIndex = _currentPage * 10 + 1;
_performSearch(_searchController.text, startIndex: nextStartIndex);
}
}

Future<void> _openPdf(String url) async {
final Uri uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Could not open the PDF file")));
}
}

void _scrollToBottom() {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
}

@override
void dispose() {
_scrollController.dispose();
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}

Widget _buildWelcomeScreen() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.search,
size: 60,
color: Theme.of(context).primaryColor,
),
),
const SizedBox(height: 24),
Text(
"Soooooogle",
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
),
const SizedBox(height: 16),
Container(
constraints: BoxConstraints(maxWidth: 400),
child: Text(
"Hi! I am Sooogle and I can help you find Notes from the whole Internet. Start by typing your search below.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color
?.withOpacity(0.8),
height: 1.4,
),
),
),
],
),
);
}

Widget _buildResultCard(PDFResult result, int index) {
return Container(
margin: EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Theme.of(context).dividerColor.withOpacity(0.3),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () => _openPdf(result.url),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.picture_as_pdf,
color: Colors.red[600],
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
result.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color:
Theme.of(context).textTheme.titleLarge?.color,
height: 1.3,
),
),
const SizedBox(height: 8),
Text(
result.description,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color
?.withOpacity(0.8),
height: 1.4,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: Text(
result.displayUrl,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
if (result.fileSize.isNotEmpty) ...[
const SizedBox(width: 8),
Container(
padding:
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color:
Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
result.fileSize,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryColor,
),
),
),
],
],
),
],
),
),
),
),
);
}

@override
Widget build(BuildContext context) {
final isDesktop = MediaQuery.of(context).size.width > 800;

return Scaffold(
appBar: const ModernNavBar(),
endDrawer: MediaQuery.of(context).size.width < 900
? const MyNavigationDrawer()
: null,
body: Column(
children: [
// Main content area
Expanded(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent) {
_loadMoreResults();
return true;
}
return false;
},
child: CustomScrollView(
controller: _scrollController,
slivers: [
// Welcome screen when no results
if (_results.isEmpty && !_isLoading && _errorMessage.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: _buildWelcomeScreen(),
),

// Error state
if (_errorMessage.isNotEmpty && _results.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.error_outline,
size: 48,
color: Colors.red[400],
),
),
const SizedBox(height: 16),
Text(
_errorMessage,
style: TextStyle(
fontSize: 16,
color: Colors.red[600],
),
),
],
),
),
),

// Results
if (_results.isNotEmpty) ...[
SliverToBoxAdapter(
child: Container(
constraints: BoxConstraints(maxWidth: 800),
margin: EdgeInsets.symmetric(
horizontal: isDesktop ? 32 : 16),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Text(
"Found ${_results.length} results",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.textTheme
.bodySmall
?.color
?.withOpacity(0.8),
),
),
),
),
),
SliverToBoxAdapter(
child: Container(
constraints: BoxConstraints(maxWidth: 800),
margin: EdgeInsets.symmetric(
horizontal: isDesktop ? 32 : 16),
child: Column(
children: [
for (int i = 0; i < _results.length; i++)
_buildResultCard(_results[i], i),

// Load more indicator
if (_hasMoreResults)
Padding(
padding: const EdgeInsets.all(24.0),
child: SpinKitThreeBounce(
color: Theme.of(context).primaryColor,
size: 24.0,
),
),
],
),
),
),
],

// Initial loading state
if (_isLoading && _results.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SpinKitRing(
color: Theme.of(context).primaryColor,
size: 50.0,
),
const SizedBox(height: 16),
Text(
"Searching for notes...",
style: TextStyle(
color: Theme.of(context)
.textTheme
.bodyMedium
?.color
?.withOpacity(0.8),
),
),
],
),
),
),
],
),
),
),

// Bottom search bar
Container(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
border: Border(
top: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.3),
width: 1,
),
),
),
child: SafeArea(
child: Container(
constraints: BoxConstraints(maxWidth: 800),
margin: EdgeInsets.symmetric(horizontal: isDesktop ? 32 : 16),
padding: EdgeInsets.symmetric(vertical: 16),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
decoration: InputDecoration(
hintText: 'Search for notes...',
hintStyle: TextStyle(
color: Theme.of(context)
.textTheme
.bodyMedium
?.color
?.withOpacity(0.5),
),
prefixIcon: Padding(
padding: EdgeInsets.only(left: 16, right: 12),
child: Icon(
Icons.search,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color
?.withOpacity(0.6),
),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: Icon(
Icons.clear,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color
?.withOpacity(0.6),
),
onPressed: () {
_searchController.clear();
setState(() {});
},
)
: null,
filled: true,
fillColor: Theme.of(context).cardColor,
contentPadding: EdgeInsets.symmetric(
horizontal: 20, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: BorderSide(
color: Theme.of(context)
.dividerColor
.withOpacity(0.3),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
onChanged: (value) => setState(() {}),
onSubmitted: (_) =>
_performSearch(_searchController.text),
),
),
),
const SizedBox(width: 12),
Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color:
Theme.of(context).primaryColor.withOpacity(0.3),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: IconButton(
onPressed: _searchController.text.isNotEmpty
? () => _performSearch(_searchController.text)
: null,
icon: Icon(
Icons.send,
color: Colors.white,
),
padding: EdgeInsets.all(12),
constraints:
BoxConstraints.tightFor(width: 44, height: 44),
),
),
],
),
),
),
),
],
),
);
}
}

class PDFResult {
final String title;
final String description;
final String url;
final String displayUrl;
final String fileSize;
final String lastModified;

PDFResult({
required this.title,
required this.description,
required this.url,
required this.displayUrl,
required this.fileSize,
required this.lastModified,
});
}

Comments

Popular posts from this blog

swapping the alternate values in given array's element ! 17/11/2022

c++ basic question

Learning stage | c++ programs