diff --git a/lib/src/app.dart b/lib/src/app.dart index 29fedf1..7b261c9 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,10 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:tabnews/src/constants.dart'; -import 'package:tabnews/src/providers/content.dart'; -import 'package:tabnews/src/providers/favorites.dart'; -import 'package:tabnews/src/providers/user.dart'; import 'package:tabnews/src/ui/layouts/tab.dart'; class App extends StatelessWidget { @@ -12,46 +8,30 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider( - lazy: false, - create: (_) => UserProvider(), + return MaterialApp( + title: 'TabNews', + debugShowCheckedModeBanner: false, + darkTheme: ThemeData.dark().copyWith( + primaryColor: Colors.white, + colorScheme: const ColorScheme.light( + primary: Colors.white60, + secondary: Colors.white60, ), - ChangeNotifierProvider( - lazy: false, - create: (_) => ContentProvider(), + textSelectionTheme: TextSelectionThemeData( + selectionColor: Colors.grey.shade700, ), - ChangeNotifierProvider( - lazy: false, - create: (_) => FavoritesProvider(), - ), - ], - child: MaterialApp( - title: 'TabNews', - debugShowCheckedModeBanner: false, - darkTheme: ThemeData.dark().copyWith( - primaryColor: Colors.white, - colorScheme: const ColorScheme.light( - primary: Colors.white60, - secondary: Colors.white60, - ), - textSelectionTheme: TextSelectionThemeData( - selectionColor: Colors.grey.shade700, - ), + ), + theme: ThemeData( + primaryColor: Colors.black, + colorScheme: const ColorScheme.light( + primary: AppColors.primaryColor, + secondary: AppColors.primaryColor, ), - theme: ThemeData( - primaryColor: Colors.black, - colorScheme: const ColorScheme.light( - primary: AppColors.primaryColor, - secondary: AppColors.primaryColor, - ), - textSelectionTheme: TextSelectionThemeData( - selectionColor: Colors.grey.shade300, - ), + textSelectionTheme: TextSelectionThemeData( + selectionColor: Colors.grey.shade300, ), - home: const TabLayout(), ), + home: const TabLayout(), ); } } diff --git a/lib/src/controllers/app.dart b/lib/src/controllers/app.dart new file mode 100644 index 0000000..6c4fc36 --- /dev/null +++ b/lib/src/controllers/app.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:tabnews/src/constants.dart'; +import 'package:tabnews/src/models/user.dart'; +import 'package:tabnews/src/preferences.dart'; + +class AppController { + static final AppController _singleton = AppController._internal(); + + factory AppController() { + return _singleton; + } + + AppController._internal(); + + static const String _loggedKey = AppConstants.loggedInKey; + static const String _authKey = AppConstants.authKey; + static const String _userKey = AppConstants.userKey; + + static ValueNotifier isLoggedIn = ValueNotifier( + Preferences.getBool(AppController._loggedKey) ?? false, + ); + static ValueNotifier auth = ValueNotifier( + Preferences.getString(AppController._authKey) ?? '', + ); + static ValueNotifier user = ValueNotifier( + AppController._getLoggedUser(), + ); + + static User _getLoggedUser() { + String pref = Preferences.getString(_userKey) ?? ''; + + if (pref.isNotEmpty) { + User user = User.fromJson(jsonDecode(pref)); + + return user; + } + + return User.fromJson({}); + } +} diff --git a/lib/src/controllers/auth.dart b/lib/src/controllers/auth.dart new file mode 100644 index 0000000..11d290d --- /dev/null +++ b/lib/src/controllers/auth.dart @@ -0,0 +1,111 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import 'package:tabnews/src/constants.dart'; +import 'package:tabnews/src/controllers/app.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; +import 'package:tabnews/src/models/user.dart'; +import 'package:tabnews/src/preferences.dart'; +import 'package:tabnews/src/services/auth.dart'; +import 'package:tabnews/src/services/http_response.dart'; + +class AuthController { + final ViewAction _view; + final AuthService _authService = AuthService(); + + final String _loggedKey = AppConstants.loggedInKey; + final String _authKey = AppConstants.authKey; + final String _userKey = AppConstants.userKey; + + final ValueNotifier _isLoading = ValueNotifier(false); + ValueNotifier get isLoading => _isLoading; + + final ValueNotifier _isRegister = ValueNotifier(false); + ValueNotifier get isRegister => _isRegister; + + AuthController(this._view); + + void login(String email, String password) async { + if (email.isEmpty || password.isEmpty) { + _view.onError(message: 'É necessário preencher todos os campos!'); + + return; + } + + _setLoading(true); + final HttpResponse auth = await _authService.postLogin(email, password); + + if (auth.ok) { + var user = await _authService.fetchUser(auth.data['token']); + + Preferences.setString(_authKey, auth.data['token']); + Preferences.setString(_userKey, jsonEncode(user.data)); + Preferences.setBool(_loggedKey, true); + + _setLoggedIn(true); + _setToken(auth.data['token']); + _setUser(User.fromJson(user.data)); + + _view.onSuccess(); + } else { + _view.onError(message: auth.message); + } + + _setLoading(false); + } + + void logout() { + Preferences.setString(_authKey, ''); + Preferences.setString(_userKey, ''); + Preferences.setBool(_loggedKey, false); + _setLoggedIn(false); + _setToken(''); + _setUser(User()); + } + + void register(String username, String email, String password) async { + if (username.isEmpty || email.isEmpty || password.isEmpty) { + _view.onError(message: 'É necessário preencher todos os campos!'); + + return; + } + + _setLoading(true); + final HttpResponse register = await _authService.postRegister( + username, + email, + password, + ); + + if (register.ok) { + _setRegister(true); + + _view.onSuccess(); + } else { + _view.onError(message: register.message); + } + + _setLoading(false); + } + + void _setLoading(bool value) { + _isLoading.value = value; + } + + void _setRegister(bool value) { + _isRegister.value = value; + } + + void _setLoggedIn(bool value) { + AppController.isLoggedIn.value = value; + } + + void _setToken(String value) { + AppController.auth.value = value; + } + + void _setUser(User value) { + AppController.user.value = value; + } +} diff --git a/lib/src/controllers/content.dart b/lib/src/controllers/content.dart new file mode 100644 index 0000000..7d39fea --- /dev/null +++ b/lib/src/controllers/content.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import 'package:tabnews/src/controllers/app.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; +import 'package:tabnews/src/services/content.dart'; +import 'package:tabnews/src/services/http_response.dart'; + +class ContentController { + final ViewAction _view; + final ContentService _contentService = ContentService(); + + final ValueNotifier _isLoading = ValueNotifier(false); + ValueNotifier get isLoading => _isLoading; + + ContentController(this._view); + + void create(String title, String body, String source) async { + if (title.isEmpty || body.isEmpty) { + _view.onError(message: 'É necessário preencher os campos obrigatórios!'); + + return; + } + + _setLoading(true); + final HttpResponse content = await _contentService.postContent( + AppController.auth.value, + title, + body, + source, + ); + + if (content.ok) { + _view.onSuccess(); + } else { + _view.onError(message: content.message); + } + + _setLoading(false); + } + + void _setLoading(bool value) { + _isLoading.value = value; + } +} diff --git a/lib/src/controllers/favorites.dart b/lib/src/controllers/favorites.dart new file mode 100644 index 0000000..7ef818a --- /dev/null +++ b/lib/src/controllers/favorites.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import 'package:tabnews/src/models/content.dart'; +import 'package:tabnews/src/preferences.dart'; + +class FavoritesController { + static final FavoritesController _singleton = FavoritesController._internal(); + + factory FavoritesController() { + return _singleton; + } + + FavoritesController._internal(); + + final ValueNotifier _isLoading = ValueNotifier(false); + ValueNotifier get isLoading => _isLoading; + + final ValueNotifier> _favorites = ValueNotifier( + FavoritesController._getSavedFavorites(), + ); + ValueNotifier> get favorites => _favorites; + + static List _getSavedFavorites() { + String pref = Preferences.getString('favorites') ?? ''; + + if (pref.isNotEmpty) { + List contents = jsonDecode(pref); + + return contents.map((e) => Content.fromJson(e)).toList(); + } + + return []; + } + + void toggle(Content content) async { + _setLoading(true); + Content copyContent = content; + + if (_favorites.value.isNotEmpty) { + if (_favorites.value + .where((element) => element.id == content.id) + .isNotEmpty) { + _favorites.value = List.from(_favorites.value) + ..removeWhere((element) => element.id == content.id); + } else { + copyContent.body = null; + _favorites.value = List.from(_favorites.value)..add(copyContent); + } + } else { + copyContent.body = null; + _favorites.value = List.from(_favorites.value)..add(copyContent); + } + + Preferences.setString('favorites', jsonEncode(_favorites.value)); + + _setLoading(false); + } + + void _setLoading(bool value) { + _isLoading.value = value; + } +} diff --git a/lib/src/controllers/user.dart b/lib/src/controllers/user.dart new file mode 100644 index 0000000..48c040f --- /dev/null +++ b/lib/src/controllers/user.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import 'package:tabnews/src/constants.dart'; +import 'package:tabnews/src/controllers/app.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; +import 'package:tabnews/src/models/user.dart'; +import 'package:tabnews/src/preferences.dart'; +import 'package:tabnews/src/services/auth.dart'; +import 'package:tabnews/src/services/http_response.dart'; + +class UserController { + final ViewAction _view; + final AuthService _authService = AuthService(); + + final String _userKey = AppConstants.userKey; + + final ValueNotifier _isLoading = ValueNotifier(false); + ValueNotifier get isLoading => _isLoading; + + final ValueNotifier _notifications = ValueNotifier( + AppController.user.value.notifications ?? true, + ); + ValueNotifier get notification => _notifications; + + UserController(this._view); + + void setNotifications(bool? value) { + _notifications.value = value ?? true; + } + + void update(String username, String email) async { + if (username.isEmpty || email.isEmpty) { + _view.onError(message: 'É necessário preencher todos os campos!'); + + return; + } + + _setLoading(true); + final HttpResponse user = await _authService.updateUser( + token: AppController.auth.value, + username: AppController.user.value.username, + newUsername: + AppController.user.value.username != username ? username : '', + newEmail: AppController.user.value.email != email ? email : '', + newNotifications: _notifications.value, + ); + + if (user.ok) { + var user = await _authService.fetchUser(AppController.auth.value); + + Preferences.setString(_userKey, jsonEncode(user.data)); + + _setUser(User.fromJson(user.data)); + + _view.onSuccess(); + } else { + _view.onError(message: user.message); + } + + _setLoading(false); + } + + void _setLoading(bool value) { + _isLoading.value = value; + } + + void _setUser(User value) { + AppController.user.value = value; + } +} diff --git a/lib/src/interfaces/view_action.dart b/lib/src/interfaces/view_action.dart new file mode 100644 index 0000000..3aea6f5 --- /dev/null +++ b/lib/src/interfaces/view_action.dart @@ -0,0 +1,4 @@ +abstract class ViewAction { + onError({required String message}); + onSuccess({dynamic data}); +} diff --git a/lib/src/providers/content.dart b/lib/src/providers/content.dart deleted file mode 100644 index 00a995c..0000000 --- a/lib/src/providers/content.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:tabnews/src/providers/user.dart'; -import 'package:tabnews/src/services/api.dart'; - -class ContentProvider extends ChangeNotifier { - static final ContentProvider _instance = ContentProvider._internal(); - - final api = Api(); - - factory ContentProvider() { - return _instance; - } - - ContentProvider._internal() { - _isLoading = false; - _isCreated = false; - } - - late bool _isLoading; - bool get isLoading => _isLoading; - - late bool _isCreated; - bool get isCreated => _isCreated; - - void _loading(bool value) { - _isLoading = value; - notifyListeners(); - } - - void create(String title, String body, String source) async { - _loading(true); - - var content = await api.postContent( - UserProvider().sessionId, - title, - body, - source, - ); - - if (content.id!.isNotEmpty) { - _isCreated = true; - } - - _isLoading = false; - notifyListeners(); - } -} diff --git a/lib/src/providers/favorites.dart b/lib/src/providers/favorites.dart deleted file mode 100644 index 84bb1ef..0000000 --- a/lib/src/providers/favorites.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:tabnews/src/models/content.dart'; -import 'package:tabnews/src/preferences.dart'; - -class FavoritesProvider extends ChangeNotifier { - static final FavoritesProvider _instance = FavoritesProvider._internal(); - - factory FavoritesProvider() { - return _instance; - } - - FavoritesProvider._internal() { - _isLoading = false; - _isCreated = false; - _favorites = []; - - _getSavedFavorites(); - } - - late bool _isLoading; - bool get isLoading => _isLoading; - - late bool _isCreated; - bool get isCreated => _isCreated; - - late List _favorites; - List get favorites => _favorites; - - void _loading(bool value) { - _isLoading = value; - notifyListeners(); - } - - void _getSavedFavorites() async { - String savedFavorties = Preferences.getString('favorites') ?? ''; - - if (savedFavorties.isNotEmpty) { - List contents = jsonDecode(savedFavorties); - for (var favorite in contents) { - _favorites.add(Content.fromJson(favorite)); - } - notifyListeners(); - } - } - - bool isFavorited(String id) { - bool isFavorite = - _favorites.where((element) => element.id == id).isNotEmpty; - - if (isFavorite) { - return true; - } - - return false; - } - - void toggle(Content content) async { - _loading(true); - Content copyContent = content; - - if (_favorites.isNotEmpty) { - if (_favorites.where((element) => element.id == content.id).isNotEmpty) { - _favorites.removeWhere((element) => element.id == content.id); - } else { - copyContent.body = null; - _favorites.add(copyContent); - } - } else { - copyContent.body = null; - _favorites.add(copyContent); - } - - Preferences.setString('favorites', jsonEncode(_favorites)); - - _isLoading = false; - notifyListeners(); - } -} diff --git a/lib/src/providers/user.dart b/lib/src/providers/user.dart deleted file mode 100644 index f6482e7..0000000 --- a/lib/src/providers/user.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; - -import 'package:tabnews/src/constants.dart'; -import 'package:tabnews/src/models/auth.dart'; -import 'package:tabnews/src/models/user.dart'; -import 'package:tabnews/src/preferences.dart'; -import 'package:tabnews/src/services/auth.dart'; - -class UserProvider extends ChangeNotifier { - static final UserProvider _instance = UserProvider._internal(); - - final api = ApiAuth(); - final String _loggedKey = AppConstants.loggedInKey; - final String _authKey = AppConstants.authKey; - final String _userKey = AppConstants.userKey; - - factory UserProvider() { - return _instance; - } - - UserProvider._internal() { - _isLoading = false; - _isRegister = false; - _loggedIn = Preferences.getBool(_loggedKey) ?? false; - - if (_loggedIn) { - _sessionId = _getSessionId(); - _user = _getUser(); - } - } - - late bool _loggedIn; - bool get loggedIn => _loggedIn; - - late String _sessionId; - String get sessionId => _sessionId; - - User? _user; - User? get user => _user; - - late bool _isLoading; - bool get isLoading => _isLoading; - - late bool _isRegister; - bool get isRegister => _isRegister; - - void login(String email, String password) async { - _loading(true); - - var auth = await api.postLogin(email, password); - - if (auth.id!.isNotEmpty) { - var user = await api.fetchUser(auth.token!); - - Preferences.setString(_authKey, jsonEncode(auth.toJson())); - Preferences.setString(_userKey, jsonEncode(user.toJson())); - Preferences.setBool(_loggedKey, true); - _loggedIn = true; - _sessionId = auth.token!; - _user = user; - } - - _isLoading = false; - notifyListeners(); - } - - void logout() { - Preferences.setString(_authKey, ''); - Preferences.setString(_userKey, ''); - Preferences.setBool(_loggedKey, false); - _loggedIn = false; - _sessionId = ''; - _user = null; - _loading(false); - - notifyListeners(); - } - - void _loading(bool value) { - _isLoading = value; - notifyListeners(); - } - - void toggleNotifications(bool? value) { - _user?.notifications = value; - notifyListeners(); - } - - void profileUpdate(String email, String username) async { - _loading(true); - - var updatedUser = await api.updateUser( - token: _sessionId, - username: _user?.username, - newUsername: _user?.username != username ? username : '', - newEmail: _user?.email != email ? email : '', - newNotifications: _user?.notifications, - ); - - if (updatedUser.id != null) { - var user = await api.fetchUser(_sessionId); - - Preferences.setString(_userKey, jsonEncode(user.toJson())); - _user = user; - } - - _isLoading = false; - notifyListeners(); - } - - void register(String username, String email, String password) async { - _loading(true); - - var register = await api.postRegister(username, email, password); - - if (register.id!.isNotEmpty) { - _isRegister = true; - } - - _isLoading = false; - notifyListeners(); - } - - String _getSessionId() { - String pref = Preferences.getString(_authKey) ?? ''; - - if (pref.isNotEmpty) { - Auth auth = Auth.fromJson(jsonDecode(pref)); - - return auth.token!; - } - - return ''; - } - - User _getUser() { - String pref = Preferences.getString(_userKey) ?? ''; - - if (pref.isNotEmpty) { - User user = User.fromJson(jsonDecode(pref)); - - return user; - } - - return User.fromJson({}); - } -} diff --git a/lib/src/services/auth.dart b/lib/src/services/auth.dart index 5467ae9..52e9307 100644 --- a/lib/src/services/auth.dart +++ b/lib/src/services/auth.dart @@ -1,13 +1,12 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:tabnews/src/models/auth.dart'; -import 'package:tabnews/src/models/user.dart'; +import 'package:tabnews/src/services/http_response.dart'; -class ApiAuth { +class AuthService { final apiUrl = 'https://www.tabnews.com.br/api/v1'; - Future postLogin(String email, String password) async { + Future postLogin(String email, String password) async { final response = await http.post( Uri.parse('$apiUrl/sessions'), headers: { @@ -19,14 +18,10 @@ class ApiAuth { }), ); - if (response.statusCode == 201) { - return Auth.fromJson(jsonDecode(response.body)); - } else { - throw Exception('Failed to login'); - } + return HttpResponse(response.statusCode, response.body); } - Future fetchUser(String token) async { + Future fetchUser(String token) async { final response = await http.get( Uri.parse('$apiUrl/user'), headers: { @@ -36,14 +31,10 @@ class ApiAuth { }, ); - if (response.statusCode == 200) { - return User.fromJson(jsonDecode(response.body)); - } else { - throw Exception('Failed to get user'); - } + return HttpResponse(response.statusCode, response.body); } - Future updateUser({ + Future updateUser({ required String token, required String? username, required String newUsername, @@ -64,14 +55,10 @@ class ApiAuth { }), ); - if (response.statusCode == 200) { - return User.fromJson(jsonDecode(response.body)); - } else { - throw Exception('Failed to get user'); - } + return HttpResponse(response.statusCode, response.body); } - Future postRegister( + Future postRegister( String username, String email, String password, @@ -88,10 +75,6 @@ class ApiAuth { }), ); - if (response.statusCode == 201) { - return User.fromJson(jsonDecode(response.body)); - } else { - throw Exception('Failed to create account'); - } + return HttpResponse(response.statusCode, response.body); } } diff --git a/lib/src/services/api.dart b/lib/src/services/content.dart similarity index 93% rename from lib/src/services/api.dart rename to lib/src/services/content.dart index 30763ed..191805b 100644 --- a/lib/src/services/api.dart +++ b/lib/src/services/content.dart @@ -4,8 +4,9 @@ import 'package:http/http.dart' as http; import 'package:tabnews/src/models/comment.dart'; import 'package:tabnews/src/models/content.dart'; +import 'package:tabnews/src/services/http_response.dart'; -class Api { +class ContentService { final apiUrl = 'https://www.tabnews.com.br/api/v1/contents'; Future> fetchContents({int page = 1}) async { @@ -93,7 +94,7 @@ class Api { } } - Future postContent( + Future postContent( String token, String title, String body, @@ -114,10 +115,6 @@ class Api { }), ); - if (response.statusCode == 201) { - return Content.fromJson(jsonDecode(response.body)); - } else { - throw Exception('Failed to create a new content'); - } + return HttpResponse(response.statusCode, response.body); } } diff --git a/lib/src/services/http_response.dart b/lib/src/services/http_response.dart new file mode 100644 index 0000000..8ad4c21 --- /dev/null +++ b/lib/src/services/http_response.dart @@ -0,0 +1,56 @@ +import 'dart:convert'; + +class HttpResponse { + bool ok = false; + String message = ""; + T? data; + final int _statusCode; + final String _body; + + HttpResponse( + this._statusCode, + this._body, + ) { + _verifyStatus(); + } + + void _verifyStatus() { + switch (_statusCode) { + case 200: + case 201: + ok = true; + break; + case 204: + ok = true; + break; + case 400: + break; + case 401: + break; + case 404: + break; + case 500: + message = "Sem conexão com servidor"; + break; + default: + message = "Erro inesperado"; + } + + _setResponse(); + } + + void _setResponse() { + if (ok) { + if (_body.isNotEmpty) { + T response = jsonDecode(_body); + data = response; + } + } else { + if (message.isEmpty) { + dynamic response = jsonDecode(_body); + + message = response['message']; + } + } + } +} diff --git a/lib/src/ui/layouts/tab.dart b/lib/src/ui/layouts/tab.dart index a3573d4..8792017 100644 --- a/lib/src/ui/layouts/tab.dart +++ b/lib/src/ui/layouts/tab.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tabnews/src/providers/user.dart'; +import 'package:tabnews/src/controllers/app.dart'; import 'package:tabnews/src/ui/pages/favorites.dart'; import 'package:tabnews/src/ui/pages/home.dart'; import 'package:tabnews/src/ui/pages/login.dart'; @@ -28,9 +27,11 @@ class _TabLayoutState extends State { HomePage(scrollController: pagesScrollController[0]), RecentsPage(scrollController: pagesScrollController[1]), const FavoritesPage(), - Consumer( - builder: (context, provider, _) => - provider.loggedIn ? const ProfilePage() : const LoginPage(), + ValueListenableBuilder( + valueListenable: AppController.isLoggedIn, + builder: (context, isLoggedIn, child) { + return isLoggedIn ? const ProfilePage() : const LoginPage(); + }, ), ]; diff --git a/lib/src/ui/pages/content.dart b/lib/src/ui/pages/content.dart index 96c1ba5..6522850 100644 --- a/lib/src/ui/pages/content.dart +++ b/lib/src/ui/pages/content.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tabnews/src/providers/favorites.dart'; import 'package:timeago/timeago.dart' as timeago; +import 'package:tabnews/src/controllers/favorites.dart'; +import 'package:tabnews/src/utils/open_link.dart'; import 'package:tabnews/src/extensions/dark_mode.dart'; import 'package:tabnews/src/models/content.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/ui/widgets/markdown.dart'; import 'package:tabnews/src/ui/widgets/comments.dart'; import 'package:tabnews/src/ui/layouts/page.dart'; @@ -26,8 +26,10 @@ class ContentPage extends StatefulWidget { } class _ContentPageState extends State { + final FavoritesController _favoritesController = FavoritesController(); + Content content = Content.fromJson({}); - final api = Api(); + final _contentService = ContentService(); final ScrollController _controller = ScrollController(); bool isLoading = true; @@ -39,7 +41,7 @@ class _ContentPageState extends State { } Future _getContent() async { - var contentResp = await api.fetchContent( + var contentResp = await _contentService.fetchContent( '${widget.username}/${widget.slug}', ); @@ -58,16 +60,21 @@ class _ContentPageState extends State { actions: isLoading ? null : [ - Consumer( - builder: (context, provider, _) => IconButton( - onPressed: () => provider.toggle( - content, - ), - icon: Icon( - provider.isFavorited(content.id!) - ? Icons.favorite - : Icons.favorite_border, - ), + IconButton( + onPressed: () => _favoritesController.toggle( + content, + ), + icon: ValueListenableBuilder( + valueListenable: _favoritesController.favorites, + builder: (context, favorites, child) { + bool isFavorited = favorites + .where((element) => element.id == content.id) + .isNotEmpty; + + return Icon( + isFavorited ? Icons.favorite : Icons.favorite_border, + ); + }, ), ), ], @@ -104,6 +111,31 @@ class _ContentPageState extends State { body: '${content.body}', controller: _controller, ), + content.sourceUrl != null + ? Row( + children: [ + Text( + 'Fonte: ', + style: const TextStyle().copyWith( + fontWeight: FontWeight.w700, + ), + ), + InkWell( + onTap: () => OpenLink.open( + content.sourceUrl, + context, + ), + child: Text( + '${content.sourceUrl}', + style: const TextStyle().copyWith( + color: Colors.blue, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ) + : const SizedBox(), const SizedBox(height: 30.0), const Divider(), const SizedBox(height: 30.0), diff --git a/lib/src/ui/pages/favorites.dart b/lib/src/ui/pages/favorites.dart index 4dd1b40..e02686e 100644 --- a/lib/src/ui/pages/favorites.dart +++ b/lib/src/ui/pages/favorites.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tabnews/src/models/content.dart'; -import 'package:tabnews/src/providers/favorites.dart'; + +import 'package:tabnews/src/controllers/favorites.dart'; import 'package:tabnews/src/ui/widgets/item_content.dart'; class FavoritesPage extends StatefulWidget { @@ -12,26 +11,29 @@ class FavoritesPage extends StatefulWidget { } class _FavoritesPageState extends State { + final FavoritesController _favoritesController = FavoritesController(); + @override Widget build(BuildContext context) { - return Consumer(builder: (context, provider, _) { - if (provider.favorites.isEmpty) { - return const Center( - child: Text('Você não possui favoritos!'), - ); - } else { - List favorites = provider.favorites; - - return ListView.builder( - padding: const EdgeInsets.all(10.0), - itemCount: favorites.length, - itemBuilder: (context, index) { - return ItemContent( - content: favorites[index], - ); - }, - ); - } - }); + return ValueListenableBuilder( + valueListenable: _favoritesController.favorites, + builder: (context, favorites, child) { + if (favorites.isEmpty) { + return const Center( + child: Text('Você não possui favoritos!'), + ); + } else { + return ListView.builder( + padding: const EdgeInsets.all(10.0), + itemCount: favorites.length, + itemBuilder: (context, index) { + return ItemContent( + content: favorites[index], + ); + }, + ); + } + }, + ); } } diff --git a/lib/src/ui/pages/home.dart b/lib/src/ui/pages/home.dart index 14d108a..09686ba 100644 --- a/lib/src/ui/pages/home.dart +++ b/lib/src/ui/pages/home.dart @@ -3,7 +3,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:tabnews/src/extensions/dark_mode.dart'; import 'package:tabnews/src/models/content.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/ui/widgets/item_content.dart'; import 'package:tabnews/src/ui/widgets/progress_indicator.dart'; @@ -18,7 +18,7 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { late List contents; - final api = Api(); + final _contentService = ContentService(); bool isLoading = true; static const _perPage = 30; @@ -42,7 +42,7 @@ class _HomePageState extends State { } Future _getContents(int page) async { - final content = await api.fetchContents(page: page); + final content = await _contentService.fetchContents(page: page); final isLastPage = content.length < _perPage; diff --git a/lib/src/ui/pages/login.dart b/lib/src/ui/pages/login.dart index 341a79b..901ec58 100644 --- a/lib/src/ui/pages/login.dart +++ b/lib/src/ui/pages/login.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:tabnews/src/constants.dart'; +import 'package:tabnews/src/controllers/auth.dart'; import 'package:tabnews/src/extensions/dark_mode.dart'; -import 'package:tabnews/src/providers/user.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; import 'package:tabnews/src/ui/pages/register.dart'; import 'package:tabnews/src/utils/navigation.dart'; @@ -14,10 +14,31 @@ class LoginPage extends StatefulWidget { State createState() => _LoginPageState(); } -class _LoginPageState extends State { +class _LoginPageState extends State implements ViewAction { + late final AuthController _authController; + + final _formKey = GlobalKey(); TextEditingController emailTextController = TextEditingController(); TextEditingController passwordTextController = TextEditingController(); - final _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + + _authController = AuthController(this); + } + + @override + onSuccess({data}) {} + + @override + onError({required String message}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + ), + ); + } @override Widget build(BuildContext context) { @@ -81,44 +102,40 @@ class _LoginPageState extends State { ], ), const SizedBox(height: 30.0), - Consumer( - builder: (context, provider, _) => ElevatedButton( - style: ButtonStyle( - elevation: MaterialStateProperty.all(0.0), - backgroundColor: MaterialStateProperty.all( - AppColors.primaryColor.withOpacity( - provider.isLoading ? 0.5 : 1.0, + ValueListenableBuilder( + valueListenable: _authController.isLoading, + builder: (context, isLoading, child) { + return ElevatedButton( + style: ButtonStyle( + elevation: MaterialStateProperty.all(0.0), + backgroundColor: MaterialStateProperty.all( + AppColors.primaryColor.withOpacity( + isLoading ? 0.5 : 1.0, + ), ), - ), - foregroundColor: MaterialStateProperty.all( - Colors.white, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ), ), ), - ), - onPressed: provider.isLoading - ? null - : () { - if (emailTextController.text.isEmpty || - passwordTextController.text.isEmpty) { - return; - } - - provider.login( - emailTextController.text, - passwordTextController.text, - ); - }, - child: Text( - provider.isLoading ? 'Aguarde...' : 'Login', - style: const TextStyle().copyWith( - fontSize: 16.0, + onPressed: isLoading + ? null + : () => _authController.login( + emailTextController.text, + passwordTextController.text, + ), + child: Text( + isLoading ? 'Aguarde...' : 'Login', + style: const TextStyle().copyWith( + fontSize: 16.0, + ), ), - ), - ), + ); + }, ), const SizedBox(height: 30.0), Row( @@ -127,8 +144,9 @@ class _LoginPageState extends State { children: [ Text( 'Ainda não possui conta?', - style: - const TextStyle().copyWith(color: Colors.grey.shade600), + style: const TextStyle().copyWith( + color: Colors.grey.shade600, + ), ), TextButton( style: const ButtonStyle().copyWith( @@ -138,7 +156,10 @@ class _LoginPageState extends State { : AppColors.primaryColor, ), ), - onPressed: () => Navigation.push(context, RegisterPage()), + onPressed: () => Navigation.push( + context, + const RegisterPage(), + ), child: const Text('Criar cadastro'), ), ], diff --git a/lib/src/ui/pages/my_contents.dart b/lib/src/ui/pages/my_contents.dart index c9d60b6..5533749 100644 --- a/lib/src/ui/pages/my_contents.dart +++ b/lib/src/ui/pages/my_contents.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:tabnews/src/controllers/app.dart'; import 'package:tabnews/src/models/content.dart'; -import 'package:tabnews/src/providers/user.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/ui/layouts/page.dart'; import 'package:tabnews/src/ui/widgets/item_content.dart'; import 'package:tabnews/src/ui/widgets/progress_indicator.dart'; @@ -17,7 +17,7 @@ class MyContentsPage extends StatefulWidget { class _MyContentsPageState extends State { late List contents; - final api = Api(); + final _contentService = ContentService(); bool isLoading = true; static const _perPage = 30; @@ -41,9 +41,9 @@ class _MyContentsPageState extends State { } Future _getContents(int page) async { - final content = await api.fetchMyContents( + final content = await _contentService.fetchMyContents( page: page, - user: '${UserProvider().user?.username}', + user: '${AppController.user.value.username}', ); final isLastPage = content.length < _perPage; diff --git a/lib/src/ui/pages/new_content.dart b/lib/src/ui/pages/new_content.dart index aaaf7b3..bc865ea 100644 --- a/lib/src/ui/pages/new_content.dart +++ b/lib/src/ui/pages/new_content.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:markdown_editable_textinput/format_markdown.dart'; import 'package:markdown_editable_textinput/markdown_text_input.dart'; -import 'package:provider/provider.dart'; import 'package:tabnews/src/constants.dart'; +import 'package:tabnews/src/controllers/content.dart'; import 'package:tabnews/src/extensions/dark_mode.dart'; -import 'package:tabnews/src/providers/content.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; import 'package:tabnews/src/ui/layouts/page.dart'; import 'package:tabnews/src/ui/pages/my_contents.dart'; import 'package:tabnews/src/ui/widgets/markdown.dart'; @@ -18,7 +18,9 @@ class NewContentPage extends StatefulWidget { State createState() => _NewContentPageState(); } -class _NewContentPageState extends State { +class _NewContentPageState extends State implements ViewAction { + late final ContentController _contentController; + TextEditingController titleTextController = TextEditingController(); TextEditingController bodyTextController = TextEditingController(); TextEditingController sourceTextController = TextEditingController(); @@ -26,6 +28,36 @@ class _NewContentPageState extends State { bool isViewMarkdown = false; + @override + void initState() { + super.initState(); + + _contentController = ContentController(this); + } + + @override + onSuccess({data}) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Conteúdo publicado com sucesso!', + ), + ), + ); + + Navigation.pop(context); + Navigation.push(context, const MyContentsPage()); + } + + @override + onError({required String message}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + ), + ); + } + @override void dispose() { titleTextController.dispose(); @@ -111,56 +143,41 @@ class _NewContentPageState extends State { controller: sourceTextController, ), const SizedBox(height: 30.0), - Consumer( - builder: (context, provider, _) => ElevatedButton( - style: ButtonStyle( - elevation: MaterialStateProperty.all(0.0), - backgroundColor: MaterialStateProperty.all( - AppColors.primaryColor.withOpacity( - provider.isLoading ? 0.5 : 1.0, + ValueListenableBuilder( + valueListenable: _contentController.isLoading, + builder: (context, isLoading, child) { + return ElevatedButton( + style: ButtonStyle( + elevation: MaterialStateProperty.all(0.0), + backgroundColor: MaterialStateProperty.all( + AppColors.primaryColor.withOpacity( + isLoading ? 0.5 : 1.0, + ), ), - ), - foregroundColor: MaterialStateProperty.all( - Colors.white, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ), ), ), - ), - onPressed: provider.isLoading - ? null - : () { - if (titleTextController.text.isEmpty || - bodyTextController.text.isEmpty) { - return; - } - - provider.create( - titleTextController.text, - bodyTextController.text, - sourceTextController.text, - ); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Conteúdo publicado com sucesso!', - ), + onPressed: isLoading + ? null + : () => _contentController.create( + titleTextController.text, + bodyTextController.text, + sourceTextController.text, ), - ); - - Navigation.pop(context); - Navigation.push(context, const MyContentsPage()); - }, - child: Text( - provider.isLoading ? 'Aguarde...' : 'Publicar', - style: const TextStyle().copyWith( - fontSize: 16.0, + child: Text( + isLoading ? 'Aguarde...' : 'Publicar', + style: const TextStyle().copyWith( + fontSize: 16.0, + ), ), - ), - ), + ); + }, ), ], ), diff --git a/lib/src/ui/pages/profile.dart b/lib/src/ui/pages/profile.dart index 96a9e4b..d8e2084 100644 --- a/lib/src/ui/pages/profile.dart +++ b/lib/src/ui/pages/profile.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tabnews/src/providers/user.dart'; +import 'package:tabnews/src/controllers/app.dart'; +import 'package:tabnews/src/controllers/auth.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; import 'package:tabnews/src/ui/pages/my_contents.dart'; import 'package:tabnews/src/ui/pages/new_content.dart'; import 'package:tabnews/src/ui/pages/profile_edit.dart'; @@ -14,7 +15,28 @@ class ProfilePage extends StatefulWidget { State createState() => _ProfilePageState(); } -class _ProfilePageState extends State { +class _ProfilePageState extends State implements ViewAction { + late final AuthController _authController; + + @override + void initState() { + super.initState(); + + _authController = AuthController(this); + } + + @override + onSuccess({data}) {} + + @override + onError({required String message}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + ), + ); + } + @override Widget build(BuildContext context) { return Container( @@ -25,12 +47,17 @@ class _ProfilePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - '${UserProvider().user?.username}', - style: const TextStyle().copyWith( - fontSize: 28.0, - fontWeight: FontWeight.w700, - ), + ValueListenableBuilder( + valueListenable: AppController.user, + builder: (context, user, child) { + return Text( + '${user.username}', + style: const TextStyle().copyWith( + fontSize: 28.0, + fontWeight: FontWeight.w700, + ), + ); + }, ), const SizedBox(height: 80.0), ListTile( @@ -43,13 +70,12 @@ class _ProfilePageState extends State { title: const Text('Publicar novo conteúdo'), ), ListTile( - onTap: () => Navigation.push(context, ProfileEditPage()), + onTap: () => Navigation.push(context, const ProfileEditPage()), title: const Text('Editar perfil'), ), const Divider(color: Colors.grey), ListTile( - onTap: () => - Provider.of(context, listen: false).logout(), + onTap: () => _authController.logout(), title: Text( 'Deslogar', style: const TextStyle().copyWith( diff --git a/lib/src/ui/pages/profile_edit.dart b/lib/src/ui/pages/profile_edit.dart index bfab836..b05279f 100644 --- a/lib/src/ui/pages/profile_edit.dart +++ b/lib/src/ui/pages/profile_edit.dart @@ -1,20 +1,48 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:tabnews/src/constants.dart'; -import 'package:tabnews/src/providers/user.dart'; +import 'package:tabnews/src/controllers/app.dart'; +import 'package:tabnews/src/controllers/user.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; import 'package:tabnews/src/ui/layouts/page.dart'; -class ProfileEditPage extends StatelessWidget { - ProfileEditPage({super.key}); +class ProfileEditPage extends StatefulWidget { + const ProfileEditPage({super.key}); + @override + State createState() => _ProfileEditPageState(); +} + +class _ProfileEditPageState extends State + implements ViewAction { + late final UserController _userController; + + final _formKey = GlobalKey(); final TextEditingController usernameTextController = TextEditingController( - text: UserProvider().user?.username, + text: AppController.user.value.username, ); final TextEditingController emailTextController = TextEditingController( - text: UserProvider().user?.email, + text: AppController.user.value.email, ); - final _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + + _userController = UserController(this); + } + + @override + onSuccess({data}) {} + + @override + onError({required String message}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + ), + ); + } @override Widget build(BuildContext context) { @@ -22,7 +50,7 @@ class ProfileEditPage extends StatelessWidget { onRefresh: () async {}, body: Center( child: Padding( - padding: const EdgeInsets.all(15.0), + padding: const EdgeInsets.all(30.0), child: Form( key: _formKey, child: Column( @@ -64,64 +92,56 @@ class ProfileEditPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Checkbox( - fillColor: MaterialStateProperty.all( - AppColors.primaryColor, - ), - value: UserProvider().user?.notifications, - onChanged: Provider.of(context) - .toggleNotifications, + ValueListenableBuilder( + valueListenable: _userController.notification, + builder: (context, notifications, child) { + return Checkbox( + fillColor: MaterialStateProperty.all( + AppColors.primaryColor, + ), + value: notifications, + onChanged: _userController.setNotifications, + ); + }, ), const Text('Receber notificações por email'), ], ), const SizedBox(height: 30.0), - Consumer( - builder: (context, provider, _) => ElevatedButton( - style: ButtonStyle( - elevation: MaterialStateProperty.all(0.0), - backgroundColor: MaterialStateProperty.all( - AppColors.primaryColor.withOpacity( - provider.isLoading ? 0.5 : 1.0, + ValueListenableBuilder( + valueListenable: _userController.isLoading, + builder: (context, isLoading, child) { + return ElevatedButton( + style: ButtonStyle( + elevation: MaterialStateProperty.all(0.0), + backgroundColor: MaterialStateProperty.all( + AppColors.primaryColor.withOpacity( + isLoading ? 0.5 : 1.0, + ), ), - ), - foregroundColor: MaterialStateProperty.all( - Colors.white, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ), ), ), - ), - onPressed: provider.isLoading - ? null - : () { - if (emailTextController.text.isEmpty || - usernameTextController.text.isEmpty) { - return; - } - - provider.profileUpdate( - emailTextController.text, - usernameTextController.text, - ); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Perfil atualizado com sucesso!', - ), + onPressed: isLoading + ? null + : () => _userController.update( + usernameTextController.text, + emailTextController.text, ), - ); - }, - child: Text( - provider.isLoading ? 'Aguarde...' : 'Salvar', - style: const TextStyle().copyWith( - fontSize: 16.0, + child: Text( + isLoading ? 'Aguarde...' : 'Salvar', + style: const TextStyle().copyWith( + fontSize: 16.0, + ), ), - ), - ), + ); + }, ), ], ), diff --git a/lib/src/ui/pages/profile_user.dart b/lib/src/ui/pages/profile_user.dart index d823d2d..31b5d04 100644 --- a/lib/src/ui/pages/profile_user.dart +++ b/lib/src/ui/pages/profile_user.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; + import 'package:tabnews/src/models/content.dart'; import 'package:tabnews/src/models/user.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/services/user.dart'; import 'package:tabnews/src/ui/layouts/page.dart'; import 'package:tabnews/src/ui/widgets/item_content.dart'; @@ -19,7 +20,7 @@ class ProfileUserPage extends StatefulWidget { class _ProfileUserPageState extends State { final userService = UserService(); - final contentService = Api(); + final _contentService = ContentService(); bool _isLoading = true; late User _user; @@ -48,7 +49,7 @@ class _ProfileUserPageState extends State { } Future _getContents(int page) async { - final content = await contentService.fetchMyContents( + final content = await _contentService.fetchMyContents( page: page, user: widget.username, ); diff --git a/lib/src/ui/pages/recents.dart b/lib/src/ui/pages/recents.dart index 7eed548..ceab3c2 100644 --- a/lib/src/ui/pages/recents.dart +++ b/lib/src/ui/pages/recents.dart @@ -3,7 +3,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:tabnews/src/extensions/dark_mode.dart'; import 'package:tabnews/src/models/content.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/ui/widgets/item_content.dart'; import 'package:tabnews/src/ui/widgets/progress_indicator.dart'; @@ -18,7 +18,7 @@ class RecentsPage extends StatefulWidget { class _RecentsPageState extends State { late List contents; - final api = Api(); + final _contentService = ContentService(); bool isLoading = true; static const _perPage = 30; @@ -42,7 +42,7 @@ class _RecentsPageState extends State { } Future _getContents(int page) async { - final content = await api.fetchContentsNew(page: page); + final content = await _contentService.fetchContentsNew(page: page); final isLastPage = content.length < _perPage; diff --git a/lib/src/ui/pages/register.dart b/lib/src/ui/pages/register.dart index 15146dd..b0586c3 100644 --- a/lib/src/ui/pages/register.dart +++ b/lib/src/ui/pages/register.dart @@ -1,18 +1,44 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:tabnews/src/constants.dart'; +import 'package:tabnews/src/controllers/auth.dart'; import 'package:tabnews/src/extensions/dark_mode.dart'; -import 'package:tabnews/src/providers/user.dart'; +import 'package:tabnews/src/interfaces/view_action.dart'; import 'package:tabnews/src/ui/widgets/top_bar.dart'; -class RegisterPage extends StatelessWidget { - RegisterPage({super.key}); +class RegisterPage extends StatefulWidget { + const RegisterPage({super.key}); + @override + State createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State implements ViewAction { + late final AuthController _authController; + + final _formKey = GlobalKey(); final TextEditingController usernameTextController = TextEditingController(); final TextEditingController emailTextController = TextEditingController(); final TextEditingController passwordTextController = TextEditingController(); - final _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + + _authController = AuthController(this); + } + + @override + onSuccess({data}) {} + + @override + onError({required String message}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + ), + ); + } @override Widget build(BuildContext context) { @@ -23,142 +49,147 @@ class RegisterPage extends StatelessWidget { key: _formKey, child: Padding( padding: const EdgeInsets.all(30.0), - child: Consumer( - builder: (context, provider, _) => Column( + child: ValueListenableBuilder( + valueListenable: _authController.isRegister, + builder: (context, isRegister, child) { + if (isRegister) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Confira seu e-mail: ${emailTextController.text}', + style: const TextStyle().copyWith( + fontSize: 20.0, + fontWeight: FontWeight.w700, + ), + ), + const Text( + 'Você receberá um link para confirmar seu cadastro e ativar a sua conta.', + ), + ], + ); + } else { + return child ?? const SizedBox(); + } + }, + child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, - children: provider.isRegister - ? [ - Text( - 'Confira seu e-mail: ${emailTextController.text}', - style: const TextStyle().copyWith( - fontSize: 20.0, - fontWeight: FontWeight.w700, - ), - ), - const Text( - 'Você receberá um link para confirmar seu cadastro e ativar a sua conta.', - ), - ] - : [ - Row( - children: [ - Expanded( - child: TextFormField( - enableSuggestions: false, - autocorrect: false, - cursorColor: context.isDarkMode + children: [ + Row( + children: [ + Expanded( + child: TextFormField( + enableSuggestions: false, + autocorrect: false, + cursorColor: context.isDarkMode + ? Colors.white + : AppColors.primaryColor, + decoration: InputDecoration( + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: context.isDarkMode ? Colors.white : AppColors.primaryColor, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: context.isDarkMode - ? Colors.white - : AppColors.primaryColor, - width: 2.0, - ), - ), - hintText: 'Nome de usuário', - ), - controller: usernameTextController, + width: 2.0, ), ), - ], + hintText: 'Nome de usuário', + ), + controller: usernameTextController, ), - Row( - children: [ - Expanded( - child: TextFormField( - keyboardType: TextInputType.emailAddress, - cursorColor: context.isDarkMode + ), + ], + ), + Row( + children: [ + Expanded( + child: TextFormField( + keyboardType: TextInputType.emailAddress, + cursorColor: context.isDarkMode + ? Colors.white + : AppColors.primaryColor, + decoration: InputDecoration( + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: context.isDarkMode ? Colors.white : AppColors.primaryColor, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: context.isDarkMode - ? Colors.white - : AppColors.primaryColor, - width: 2.0, - ), - ), - hintText: 'Email', - ), - controller: emailTextController, + width: 2.0, ), ), - ], + hintText: 'Email', + ), + controller: emailTextController, ), - Row( - children: [ - Expanded( - child: TextFormField( - enableSuggestions: false, - autocorrect: false, - obscureText: true, - cursorColor: context.isDarkMode + ), + ], + ), + Row( + children: [ + Expanded( + child: TextFormField( + enableSuggestions: false, + autocorrect: false, + obscureText: true, + cursorColor: context.isDarkMode + ? Colors.white + : AppColors.primaryColor, + decoration: InputDecoration( + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: context.isDarkMode ? Colors.white : AppColors.primaryColor, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: context.isDarkMode - ? Colors.white - : AppColors.primaryColor, - width: 2.0, - ), - ), - hintText: 'Senha', - ), - controller: passwordTextController, + width: 2.0, ), ), - ], + hintText: 'Senha', + ), + controller: passwordTextController, ), - const SizedBox(height: 30.0), - ElevatedButton( - style: ButtonStyle( - elevation: MaterialStateProperty.all(0.0), - backgroundColor: MaterialStateProperty.all( - AppColors.primaryColor.withOpacity( - provider.isLoading ? 0.5 : 1.0, - ), - ), - foregroundColor: MaterialStateProperty.all( - Colors.white, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0), - ), + ), + ], + ), + const SizedBox(height: 30.0), + ValueListenableBuilder( + valueListenable: _authController.isLoading, + builder: (context, isLoading, child) { + return ElevatedButton( + style: ButtonStyle( + elevation: MaterialStateProperty.all(0.0), + backgroundColor: MaterialStateProperty.all( + AppColors.primaryColor.withOpacity( + isLoading ? 0.5 : 1.0, ), ), - onPressed: provider.isLoading - ? null - : () { - if (usernameTextController.text.isEmpty || - emailTextController.text.isEmpty || - passwordTextController.text.isEmpty) { - return; - } - - provider.register( - usernameTextController.text, - emailTextController.text, - passwordTextController.text, - ); - }, - child: Text( - provider.isLoading - ? 'Aguarde...' - : 'Criar cadastro', - style: const TextStyle().copyWith( - fontSize: 16.0, + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), ), ), ), - ], + onPressed: isLoading + ? null + : () => _authController.register( + usernameTextController.text, + emailTextController.text, + passwordTextController.text, + ), + child: Text( + isLoading ? 'Aguarde...' : 'Criar cadastro', + style: const TextStyle().copyWith( + fontSize: 16.0, + ), + ), + ); + }, + ), + ], ), ), ), diff --git a/lib/src/ui/widgets/comments.dart b/lib/src/ui/widgets/comments.dart index a3b1de1..49bc315 100644 --- a/lib/src/ui/widgets/comments.dart +++ b/lib/src/ui/widgets/comments.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:timeago/timeago.dart' as timeago; import 'package:tabnews/src/models/comment.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/ui/widgets/item_comment.dart'; class CommentsRootWidget extends StatefulWidget { @@ -21,7 +21,7 @@ class CommentsRootWidget extends StatefulWidget { class _CommentsRootWidgetState extends State { List comments = []; - final api = Api(); + final api = ContentService(); @override void initState() { diff --git a/lib/src/ui/widgets/comments_children.dart b/lib/src/ui/widgets/comments_children.dart index c0282bc..653de3b 100644 --- a/lib/src/ui/widgets/comments_children.dart +++ b/lib/src/ui/widgets/comments_children.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:timeago/timeago.dart' as timeago; import 'package:tabnews/src/models/comment.dart'; -import 'package:tabnews/src/services/api.dart'; +import 'package:tabnews/src/services/content.dart'; import 'package:tabnews/src/ui/widgets/item_comment.dart'; class CommentsWidget extends StatefulWidget { @@ -20,7 +20,7 @@ class CommentsWidget extends StatefulWidget { } class _CommentsWidgetState extends State { - final api = Api(); + final api = ContentService(); @override Widget build(BuildContext context) { diff --git a/pubspec.lock b/pubspec.lock index 730e789..be929f1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -198,13 +198,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" path: dependency: transitive description: @@ -275,13 +268,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" - provider: - dependency: "direct main" - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.4" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 24946cf..0cd36e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,7 +40,6 @@ dependencies: flutter_markdown: ^0.6.13 http: ^0.13.5 infinite_scroll_pagination: ^3.2.0 - provider: ^6.0.4 shared_preferences: ^2.0.15 markdown: ^6.0.1 markdown_editable_textinput: ^2.1.0