Skip to content

Commit

Permalink
Dividing to a proper MVVM structure
Browse files Browse the repository at this point in the history
  • Loading branch information
TheGuyDangerous committed Oct 6, 2024
1 parent c558bfc commit e432657
Show file tree
Hide file tree
Showing 38 changed files with 2,480 additions and 153 deletions.
3 changes: 3 additions & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
12 changes: 2 additions & 10 deletions lib/custom_page_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ class CustomPageRoute extends PageRouteBuilder {
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
),
),
return FadeTransition(
opacity: animation,
child: child,
);
}
Expand Down
98 changes: 21 additions & 77 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,90 +1,34 @@
import 'package:flutter/material.dart';
import 'screens/search_screen.dart';
import 'screens/library_screen.dart';
import 'screens/settings_screen.dart';
import 'package:iconsax/iconsax.dart';
import 'package:provider/provider.dart';
import 'package:freelexity/screens/splash_screen.dart';
import 'package:freelexity/theme/app_theme.dart';
import 'package:freelexity/theme_provider.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
runApp(
ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child: const MyApp(),
),
);
}

class MyApp extends StatelessWidget {
const MyApp({super.key});
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Freelexity',
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.black,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
textTheme: const TextTheme(
bodyLarge: TextStyle(fontFamily: 'Raleway'),
bodyMedium: TextStyle(fontFamily: 'Raleway'),
titleLarge:
TextStyle(fontFamily: 'Raleway', fontWeight: FontWeight.bold),
titleMedium:
TextStyle(fontFamily: 'Raleway', fontWeight: FontWeight.bold),
titleSmall:
TextStyle(fontFamily: 'Raleway', fontWeight: FontWeight.bold),
).apply(
bodyColor: Colors.white,
displayColor: Colors.white,
),
),
home: const HomeScreen(),
);
}
}

class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});

@override
HomeScreenState createState() => HomeScreenState();
}

class HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _screens = [
const SearchScreen(),
const LibraryScreen(),
const SettingsScreen(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Iconsax.search_normal),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Iconsax.book_1),
label: 'Library',
),
BottomNavigationBarItem(
icon: Icon(Iconsax.setting_2),
label: 'Settings',
),
],
backgroundColor: Colors.grey[900],
selectedItemColor: Colors.white,
unselectedItemColor: Colors.grey,
),
return Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return MaterialApp(
title: 'Freelexity',
theme: themeProvider.isDarkMode
? AppTheme.darkTheme
: AppTheme.lightTheme,
home: SplashScreen(),
);
},
);
}
}
69 changes: 69 additions & 0 deletions lib/screens/home/home_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:iconsax/iconsax.dart';
import 'package:provider/provider.dart';
import 'package:freelexity/screens/search/search_screen.dart';
import 'package:freelexity/screens/library/library_screen.dart';
import 'package:freelexity/screens/settings/settings_screen.dart';
import 'package:freelexity/theme_provider.dart';
import 'package:flutter/services.dart';

class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);

@override
HomeScreenState createState() => HomeScreenState();
}

class HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _screens = [
const SearchScreen(),
const LibraryScreen(),
const SettingsScreen(),
];

@override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);

return Scaffold(
body: _screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Iconsax.search_normal),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Iconsax.book_1),
label: 'Library',
),
BottomNavigationBarItem(
icon: Icon(Iconsax.setting_2),
label: 'Settings',
),
],
backgroundColor: themeProvider.isDarkMode ? Colors.black : Colors.white,
selectedItemColor:
themeProvider.isDarkMode ? Colors.white : Colors.black,
unselectedItemColor: Colors.grey,
),
);
}

void _shareApp() {
const String shareText =
"Try Freelexity:\nhttps://www.github.com/TheGuyDangerous/Freelexity";
Clipboard.setData(ClipboardData(text: shareText)).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('App link copied to clipboard')),
);
});
}
}
9 changes: 9 additions & 0 deletions lib/screens/library/library_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
import 'library_screen_state.dart';

class LibraryScreen extends StatefulWidget {
const LibraryScreen({Key? key}) : super(key: key);

@override
State<LibraryScreen> createState() => LibraryScreenState();
}
164 changes: 164 additions & 0 deletions lib/screens/library/library_screen_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import '../../widgets/library/history_list.dart';
import '../../widgets/library/empty_state.dart';
import '../../widgets/library/incognito_message.dart';
import '../../services/search_service.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:provider/provider.dart';
import '../../theme_provider.dart';
import 'library_screen.dart';
import 'package:intl/intl.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

const int MAX_HISTORY_ITEMS = 50;

class LibraryScreenState extends State<LibraryScreen> {
List<Map<String, dynamic>> _searchHistory = [];
final SearchService _searchService = SearchService();
bool _isIncognitoMode = false;
final RefreshController _refreshController =
RefreshController(initialRefresh: false);

@override
void initState() {
super.initState();
_loadSearchHistory();
}

Future<void> _loadSearchHistory() async {
final prefs = await SharedPreferences.getInstance();
_isIncognitoMode = prefs.getBool('incognitoMode') ?? false;
if (!_isIncognitoMode) {
final history = prefs.getStringList('search_history') ?? [];
setState(() {
_searchHistory = history
.map((item) => json.decode(item) as Map<String, dynamic>)
.toList();
});
} else {
setState(() {
_searchHistory = [];
});
}
}

@override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);

return Scaffold(
appBar: AppBar(
title: Text('Library',
style:
TextStyle(fontFamily: 'Raleway', fontWeight: FontWeight.bold)),
backgroundColor: themeProvider.isDarkMode ? Colors.black : Colors.white,
foregroundColor: themeProvider.isDarkMode ? Colors.white : Colors.black,
elevation: 0,
actions: [
if (_searchHistory.isNotEmpty)
IconButton(
icon: Icon(Icons.delete_sweep),
onPressed: _clearAllHistory,
),
],
),
body: SmartRefresher(
controller: _refreshController,
onRefresh: _onRefresh,
child: _isIncognitoMode
? IncognitoMessage()
: _searchHistory.isEmpty
? EmptyState()
: HistoryList(
searchHistory: _searchHistory,
onDeleteItem: _deleteHistoryItem,
onClearAll: _clearAllHistory,
onItemTap: (query) =>
_searchService.performSearch(context, query),
),
),
);
}

void _deleteHistoryItem(int index) async {
setState(() {
_searchHistory.removeAt(index);
});
await _saveSearchHistory();
}

void _clearAllHistory() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Clear All History'),
content: Text('Are you sure you want to clear all search history?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('Clear'),
),
],
),
);

if (confirmed == true) {
setState(() {
_searchHistory.clear();
});
await _saveSearchHistory();
}
}

Future<void> _saveSearchHistory() async {
final prefs = await SharedPreferences.getInstance();
if (_searchHistory.length > MAX_HISTORY_ITEMS) {
_searchHistory = _searchHistory.sublist(0, MAX_HISTORY_ITEMS);
}
final history = _searchHistory.map((item) => json.encode(item)).toList();
await prefs.setStringList('search_history', history);
}

void _onRefresh() async {
await _loadSearchHistory();
_refreshController.refreshCompleted();
}

String _formatTimestamp(String timestamp) {
final date = DateTime.parse(timestamp);
final now = DateTime.now();
final difference = now.difference(date);

if (difference.inDays == 0) {
return DateFormat.jm().format(date);
} else if (difference.inDays == 1) {
return 'Yesterday';
} else if (difference.inDays < 7) {
return DateFormat.E().format(date);
} else {
return DateFormat.yMMMd().format(date);
}
}

@override
void dispose() {
_refreshController.dispose();
super.dispose();
}

Future<void> _exportSearchHistory() async {
final String historyJson = jsonEncode(_searchHistory);
// Implement file writing logic here, or use a package like path_provider to save the file
// For now, we'll just print the JSON
print(historyJson);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Search history exported')),
);
}
}
Loading

0 comments on commit e432657

Please sign in to comment.