import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../models/article.dart';

class ArticleScreen extends StatefulWidget {
  @override
  _ArticleScreenState createState() => _ArticleScreenState();
}

class _ArticleScreenState extends State<ArticleScreen> {
  late Future<List<Article>> _articles;

  @override
  void initState() {
    super.initState();
    _articles = fetchArticles();
  }

  Future<List<Article>> fetchArticles() async {
    final response =
        await http.get(Uri.parse('<http://127.0.0.1:8000/community/>'));

    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => Article.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load articles');
    }
  }

  void _showArticleOptions(int articleId) {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext context) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ListTile(
              leading: Icon(Icons.edit),
              title: Text('Edit'),
              onTap: () {
                Navigator.pushNamed(context, '/edit_article',
                    arguments: articleId);
              },
            ),
            ListTile(
              leading: Icon(Icons.delete),
              title: Text('Delete'),
              onTap: () {
                _deleteArticle(articleId);
              },
            ),
          ],
        );
      },
    );
  }

  Future<void> _deleteArticle(int articleId) async {
    final response = await http
        .delete(Uri.parse('<http://127.0.0.1:8000/community/$articleId/>'));

    if (response.statusCode == 204) {
      setState(() {
        _articles = fetchArticles();
      });
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Article deleted')));
    } else {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Failed to delete article')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Community Posts'),
      ),
      body: FutureBuilder<List<Article>>(
        future: _articles,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('No posts available'));
          }

          final articles = snapshot.data!;

          return ListView.separated(
            separatorBuilder: (context, index) =>
                Divider(color: Colors.grey[300]),
            itemCount: articles.length,
            itemBuilder: (context, index) {
              final article = articles[index];
              return ListTile(
                contentPadding: EdgeInsets.all(16),
                title: Text(article.user),
                subtitle: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(article.content),
                    if (article.image != null) Image.network(article.image!),
                    SizedBox(height: 8),
                    Text('${article.createdAt} | ${article.updatedAt}'),
                    Row(
                      children: [
                        Icon(Icons.comment, size: 16),
                        SizedBox(width: 4),
                        Text('${article.commentCount}'),
                        SizedBox(width: 16),
                        Icon(Icons.thumb_up, size: 16),
                        SizedBox(width: 4),
                        Text('${article.likesCount}'),
                      ],
                    ),
                  ],
                ),
                trailing: IconButton(
                  icon: Icon(Icons.more_vert),
                  onPressed: () => _showArticleOptions(article.articleId),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.pushNamed(context, '/create_article');
        },
        child: Icon(Icons.add),
        tooltip: 'Create Article',
      ),
    );
  }
}

import 'package:flutter/material.dart';
import 'package:fithubfe/screens/article_screen.dart';
import 'package:fithubfe/screens/comment_screen.dart';
import 'package:fithubfe/screens/create_article_screen.dart';
import 'package:fithubfe/screens/edit_article_screen.dart';
import 'package:fithubfe/screens/signup_screen.dart';
import 'package:fithubfe/screens/user_profile_screen.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: '/signup',
      onGenerateRoute: (settings) {
        // 여기서 경로를 다이나믹하게 처리
        switch (settings.name) {
          case '/signup':
            return MaterialPageRoute(builder: (_) => SignupScreen());
          case '/':
            return MaterialPageRoute(builder: (_) => ArticleScreen());
          case '/create_article':
            return MaterialPageRoute(builder: (_) => CreateArticleScreen());
          case '/edit_article':
            final articleId = settings.arguments as int;
            return MaterialPageRoute(
              builder: (_) => EditArticleScreen(articleId: articleId),
            );
          case '/comments':
            final articleId = settings.arguments as int;
            return MaterialPageRoute(
              builder: (_) => CommentScreen(articleId: articleId),
            );
          case '/profile':
            return MaterialPageRoute(builder: (_) => UserProfileScreen(id: 4));
          default:
            return MaterialPageRoute(
              builder: (_) => Scaffold(
                body: Center(child: Text('404 Not Found')),
              ),
            );
        }
      },
    );
  }
}

// lib/screens/login_screen.dart

import 'package:fithubfe/widgets/custom_textfield.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../services/api_service.dart';
import '../widgets/custom_textfield.dart';
import '../widgets/toggle_button.dart';

class LoginScreen extends StatefulWidget {
  final VoidCallback onSignUpPressed;

  LoginScreen({required this.onSignUpPressed});

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

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  final ApiService _apiService = ApiService();

  Future<void> _login() async {
    if (_formKey.currentState?.validate() ?? false) {
      final email = _emailController.text;
      final password = _passwordController.text;

      final response = await _apiService.login(email, password);

      if (response.containsKey('access')) {
        // Handle successful login
        Navigator.pushReplacementNamed(
            context, '/home'); // Navigate to home screen
      } else {
        // Handle login failure
        ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('로그인 실패: ${response['message']}')));
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('로그인'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CustomTextField(
                hintText: '이메일',
                controller: _emailController,
              ),
              SizedBox(height: 16),
              CustomTextField(
                hintText: '비밀번호',
                controller: _passwordController,
                obscureText: true,
              ),
              SizedBox(height: 24),
              ElevatedButton(
                onPressed: _login,
                child: Text('로그인'),
              ),
              SizedBox(height: 16),
              ToggleButton(
                text: '처음이에요? 회원가입',
                onPressed: widget.onSignUpPressed,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// lib/screens/signup_screen.dart

import 'package:fithubfe/widgets/custom_textfield.dart';
import 'package:flutter/material.dart';
import '../services/api_service.dart';
import '../widgets/custom_textfield.dart';
import '../widgets/toggle_button.dart';

class SignUpScreen extends StatefulWidget {
  final VoidCallback onLoginPressed;

  SignUpScreen({required this.onLoginPressed});

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

class _SignUpScreenState extends State<SignUpScreen> {
  final _emailController = TextEditingController();
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  final ApiService _apiService = ApiService();

  Future<void> _signup() async {
    if (_formKey.currentState?.validate() ?? false) {
      final email = _emailController.text;
      final username = _usernameController.text;
      final password = _passwordController.text;

      final response = await _apiService.signup(email, password, username);

      if (response.containsKey('access')) {
        // Handle successful signup
        Navigator.pushReplacementNamed(
            context, '/home'); // Navigate to home screen
      } else {
        // Handle signup failure
        ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('회원가입 실패: ${response['message']}')));
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('회원가입'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CustomTextField(
                hintText: '이메일',
                controller: _emailController,
              ),
              SizedBox(height: 16),
              CustomTextField(
                hintText: '사용자 이름',
                controller: _usernameController,
              ),
              SizedBox(height: 16),
              CustomTextField(
                hintText: '비밀번호',
                controller: _passwordController,
                obscureText: true,
              ),
              SizedBox(height: 24),
              ElevatedButton(
                onPressed: _signup,
                child: Text('회원가입'),
              ),
              SizedBox(height: 16),
              ToggleButton(
                text: '계정이 있어요? 로그인',
                onPressed: widget.onLoginPressed,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

전문가 구분 o

import 'dart:convert';
import 'package:fithubfe/screens/comment_screen.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import '../models/article.dart';
import '../models/user.dart';

import 'package:flutter/material.dart';

class ArticleScreen extends StatefulWidget {
  @override
  _ArticleScreenState createState() => _ArticleScreenState();
}

class _ArticleScreenState extends State<ArticleScreen> {
  List<Article> _articles = [];
  List<Article> _expertArticles = [];
  bool _isLoading = false;

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

  Future<void> _fetchArticles() async {
    setState(() {
      _isLoading = true;
    });

    // API 호출을 통해 게시물 불러오기
    // 일반 게시물과 전문가 게시물을 구분하여 리스트에 추가
    final response =
        await http.get(Uri.parse('<http://127.0.0.1:8000/community/>'));

    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      setState(() {
        _articles = data.map((item) => Article.fromJson(item)).toList();
        _expertArticles =
            _articles.where((article) => article.user.isExpert).toList();
        _isLoading = false;
      });
    } else {
      setState(() {
        _isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to load articles')),
      );
    }
  }

  Future<void> _likeArticle(int articleId, bool isLikedByUser) async {
    final response = await http.post(
      Uri.parse('<http://127.0.0.1:8000/community/$articleId/like/>'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
    );

    if (response.statusCode == 200) {
      setState(() {
        _articles = _fetchArticles() as List<Article>;
      });
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Article liked')));
    } else {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Failed to like article')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Articles'),
      ),
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : ListView(
              children: [
                if (_expertArticles.isNotEmpty) _buildExpertArticles(),
                _buildGeneralArticles(),
              ],
            ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await Navigator.pushNamed(context, '/create_article');
          if (result == true) {
            _fetchArticles(); // 새 게시물이 추가된 후 데이터를 다시 불러옴
          }
        },
        child: Icon(Icons.add),
        tooltip: 'Create Article',
      ),
    );
  }

  Widget _buildExpertArticles() {
    return Card(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(15.0),
      ),
      elevation: 5,
      margin: EdgeInsets.all(10.0),
      child: Container(
        height: 200, // 카드 높이 설정
        child: PageView.builder(
          itemCount: _expertArticles.length,
          itemBuilder: (context, index) {
            final article = _expertArticles[index];
            return GestureDetector(
              onTap: () {
                Navigator.pushNamed(
                  context,
                  '/article_detail',
                  arguments: article.articleId,
                );
              },
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      article.user.username,
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    SizedBox(height: 10),
                    Text(article.content),
                    if (article.image != null) Image.network(article.image!),
                  ],
                ),
              ),
            );
          },
        ),
      ),
    );
  }

  Widget _buildGeneralArticles() {
    return ListView.builder(
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
      itemCount: _articles.length,
      itemBuilder: (context, index) {
        final article = _articles[index];
        return Column(
          children: [
            ListTile(
              leading: CircleAvatar(
                backgroundImage: article.user.profileImage != null
                    ? NetworkImage(article.user.profileImage)
                    : AssetImage('assets/default_profile.png'),
              ),
              title: Text(article.user.username),
              subtitle: Text(
                  '@${article.user.id} · ${_calculateElapsedTime(article.createdAt)}'),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(article.content),
                  if (article.image != null)
                    Padding(
                      padding: const EdgeInsets.only(top: 10.0),
                      child: Image.network(article.image!),
                    ),
                  SizedBox(height: 10),
                  Text('Updated at: ${_formatDate(article.updatedAt)}'),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      IconButton(
                        icon: Icon(Icons.comment),
                        onPressed: () {
                          _showCommentBottomSheet(context, article.articleId);
                        },
                      ),
                      Text('${article.commentCount}'),
                      IconButton(
                        icon: Icon(
                          article.isLikedByUser
                              ? Icons.favorite
                              : Icons.favorite_border,
                          color: article.isLikedByUser ? Colors.red : null,
                        ),
                        onPressed: () => _likeArticle(
                            article.articleId, article.isLikedByUser),
                      ),
                      Text('${article.likesCount}'),
                    ],
                  ),
                ],
              ),
            ),
            Divider(),
          ],
        );
      },
    );
  }

  String _calculateElapsedTime(DateTime createdAt) {
    final difference = DateTime.now().difference(createdAt);
    if (difference.inDays > 0) return '${difference.inDays}d';
    if (difference.inHours > 0) return '${difference.inHours}h';
    return '${difference.inMinutes}m';
  }

  String _formatDate(DateTime date) {
    return DateFormat('yyyy-MM-dd HH:mm').format(date);
  }

  void _showCommentBottomSheet(BuildContext context, int articleId) {
    showModalBottomSheet(
      context: context,
      builder: (context) {
        return CommentScreen(articleId: articleId);
      },
    );
  }
}

shared perferences 없는 버전

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class LoginScreen extends StatefulWidget {
  final VoidCallback onSignUpPressed;

  const LoginScreen({super.key, required this.onSignUpPressed});

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

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();

  Future<void> _login() async {
    if (_formKey.currentState?.validate() ?? false) {
      final email = _emailController.text;
      final password = _passwordController.text;

      try {
        final response = await http.post(
          Uri.parse('<http://10.0.2.2:8000/users/login/>'),
          headers: <String, String>{
            'Content-Type': 'application/json; charset=UTF-8',
          },
          body: jsonEncode(<String, String>{
            'email': email,
            'password': password,
          }),
        );

        if (response.statusCode == 200) {
          const snackBar = SnackBar(content: Text('로그인 성공'));
          if (mounted) {
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
            Navigator.pushReplacementNamed(context, '/home');
          }
        } else {
          final errorMessage = json.decode(response.body)['message'];
          final snackBar = SnackBar(content: Text('Error: $errorMessage'));
          if (mounted) {
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          }
        }
      } catch (e) {
        final snackBar = SnackBar(content: Text('Exception: ${e.toString()}'));
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(snackBar);
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('로그인'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(labelText: '이메일'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return '이메일을 입력해 주세요';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _passwordController,
                decoration: const InputDecoration(labelText: '비밀번호'),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return '비밀번호를 입력해 주세요';
                  }
                  if (value.length < 8) {
                    return '비밀번호는 최소 8자리 이상이어야 합니다';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: _login,
                child: const Text('로그인'),
              ),
              TextButton(
                onPressed: widget.onSignUpPressed,
                child: const Text('처음이에요? 회원가입'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}