Baştan sona Twitter'ı yazalım #3 (Flutter, Riverpod, Fpdart, Appwrite)

Gauloran

Moderasyon Tim Lideri
7 Tem 2013
8,266
751
BAŞTAN SONA TWITTER'I YAZALIM #3 (Flutter, Riverpod, Fpdart, Appwrite)

Selamlar serinin ilk iki konusu:


En son auth_controller.dart dosyasını yazıyorduk devam edelim. AuthController classını StateNotifier<bool> dan extendleyip devam ediyoruz 2 şey isteyecek bizdenbiri AuthAPI nesnesi diğeri userAPI nesnesi AuthAPI bir önceki konuda yazmıştık yazdığımız yere kadar elinizde olanlarla devam edin. Constructorda bir AuthAPI gerektiğini söylüyoruz ve _authAPI'a veriyoruz bunu. Burada auth_controller.dartı yazmadaki amaç UI ile iletişim kuracak olan katman controllerımız örnek olarak currentUser metodunu oluşturuyoruz bu Appwrite package'dan gelen bir User beklemekte _authAPI nesnesi AuthAPI'ın nesnesi olduğu için AuthAPI'daki metodlara erişebiliyoruz currentUser metodu apideki currentUserAccount metoduna erişecek o da zaten Future içerisinde belirttiğimiz şeyi döndürdüğü için sorun yok

Kod:
class AuthController extends StateNotifier<bool> {
  final AuthAPI _authAPI;
  final UserAPI _userAPI;

  //! it will manage the bool value
  AuthController({required AuthAPI authAPI, required UserAPI userAPI})
      : _authAPI = authAPI,
        _userAPI = userAPI,
        super(false); //! default value is FALSE

  Future<model.User?> currentUser() => _authAPI.currentUserAccount();

Burada signUp artık kayıt olma metodunu UI ile iletişim kuracak şekilde yazalım signUp metodumuz geriye bir şey döndürmeyeceği için void olarak belirtiyoruz. Ardından String olarak email, password bekliyoruz contexti istiyoruz ben kayıt olurken kullanıcılara yaş ve ülke seçeneği de seçtirdiğim için onları da istedim siz burayı daha sade de tutabilirsiniz veya kullanıcılardan başka şeyler de isteyebilirsiniz. asenkron şekilde state'i yönetmek için true ya çekiyoruz önce ardından apideki signUp metodunu çalıştırıyoruz o metod sadece email ve pass bekliyordu burada verilenleri giriyoruz state'i false'a çekiyoruz ve fpdart package'ını kullanıyoruz snackbar gösterimi yapıyoruz ve bir UserModel oluşturuyoruz bu bildiğimiz kullanıcı model sınıfından nesne oluşturuyoruz benim model sınıfımda email name followres following profilPic gibi detaylı alanlar var dediğim gibi bu model sınıfı dahil buralar sadeleştirilebilir veya daha fazla şey eklenebilir. Hatta kullanıcılara coin tanımlayıp default olarak 20 coin vermişim

Kod:
 void signUp({
    required String email,
    required String password,
    required BuildContext context,
    required String age,
    required String country,
  }) async {
    state = true;
    final res = await _authAPI.signUp(email: email, password: password);
    state = false;
    res.fold((l) => showSnackBar(context, l.message), (user) async {
      UserModel userModel = UserModel(
        email: email,
        name: getNameFromEmail(email),
        followers: const [],
        following: const [],
        profilePic: '',
        bannerPic: '',
        uid: user.$id,
        bio: '',
        isTwitterBlue: false,
        age: age,
        country: country,
        coin: "20",
        totalTime: "0",
      );

      final res2 = await _userAPI.saveUserData(userModel);
      res2.fold((l) => showSnackBar(context, l.message), (r) {
        showSnackBar(context, AppTexts.accCreated);
        Navigator.pushReplacement(
          context,
          LoginView.route(),
        );
      });
    });
  }

Ardından oluşturulan bu model sınıfındaki nesneyi de userAPI'deki kullanıcıyı db'ye kaydetmek için saveUserData şeklinde bir metod yazmıştık (henüz yazmadıysak sıkıntı yok yorum satırı olarak bırakılıp yazdıktan sonra hata almadan devam edilebilir. O metodu da çalıştırdıktan sonra yaptığımız tek şey loginview sayfasına yönlendirme yapmak ondan önce de bir snackbar göstermişiz bu snackbara çok da gerek yok isterseniz silin.

Kod:
void login({
    required String email,
    required String password,
    required BuildContext context,
  }) async {
    state = true;
    final res = await _authAPI.login(email: email, password: password);
    state = false;
    showSnackBar(context, AppTexts.tryingLogin);
    await Future.delayed(const Duration(seconds: 2));
    res.fold((l) => showSnackBar(context, l.message), (r) {
      Navigator.pushReplacement(context, HomeView.route());
    });
  }

login metodu da benzer şekilde sadece yapacağı iş state i true ya çekip apideki login metodunu kullanıp state'i false'a geri çekmek ardından 2 saniye geciktirmişiz bunu yapmanızı tavsiye etmem ama muhtemelen bir şeyler yolunda gitmediği için bekletmişim bir snackbar gösterip ardından tekrar fpdart package'ından faydalanıp ana sayfaya yönlendirme yapmışız yani eğer başarılı olursa giriş işlemi ana sayfaya gidilecek.

En önemli metodlar bunlar kullanıcı bilgisini getir ve çıkış yap metodları da var sonradan ekleriz. Şu anda başarılı bir şekilde giriş yapılabiliyor. Çalışan bir twitter login ve signup screen'i yapmış olduk. Şimdi ana sayfada giriş yapıldıktan sonra kullanıcıların postlarını çekmek istediğimiz için post_api.dart dosyasını yazmamız gerekli. Ondan önce home_view.dart'ın UI tarafını yazalım

HomeView olarak bir consumerstatefulwidget oluşturup riverpod package'ından yararlanıyoruz

Kod:
class HomeView extends ConsumerStatefulWidget {
  static route() => MaterialPageRoute(
        builder: (context) => const HomeView(),
      );

  const HomeView({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _HomeViewState();
}
Kod:
class _HomeViewState extends ConsumerState<HomeView> {
  int _page = 0;

  void onPageChange(int index) {
    setState(() {
      _page = index;
    });
  }

  onCreatePost() {
    Navigator.push(context, CreatePostScreen.route());
  }

  @override
  Widget build(BuildContext context) {
    final appThemeState = ref.watch(appThemeStateNotifier);
    return Scaffold(

HomeView sayfasında scaffold altında IndexedStack kullanacağımız için onPageChange metodunu yukarıya yazdık o metod bir index alıyor int değer olarak ve sayfayı yenileyip _page'e index'i eşitliyor basitçe. İlk sayfa da 0 zaten. build metodunun hemen altında appThemeState diye bir kullanım yaptım o aslında şu anki tema darksa şunu yap lightsa şunu yap şeklinde işlemler yapmak için şimdilik es geçilebilir.

Burada bir appbar gösterilecek ve daha sonra indexed stack içerisinde belli başlı şeyler olacak

Kod:
return Scaffold(
      appBar: _page == 0
          ? AppBar(
              actions: [
                
              ],
              title: SvgPicture.asset(
                AssetsConstants.kooginLogo,
                colorFilter: ColorFilter.mode(
                    appThemeState.isDarkModeEnabled ? Pallete.whiteColor : Pallete.blackColor, BlendMode.srcIn),
                height: 35,
              ),
              centerTitle: true,
            )
          : null,
      body: IndexedStack(index: _page, children: UIConstants.bottomTabBarPages),
      floatingActionButton: FloatingActionButton(
        mini: true,
        onPressed: onCreatePost,
        child: const Icon(Icons.add, color: Pallete.blackColor),
      ),
      bottomNavigationBar: CupertinoTabBar(
        iconSize: 25,
        onTap: onPageChange,
        currentIndex: _page,
        backgroundColor: appThemeState.isDarkModeEnabled ? Pallete.blackColor : Colors.white,
        items: [
          BottomNavigationBarItem(
            icon: _page == 0
                ? const Icon(Icons.home, color: Pallete.blueColor)
                : const Icon(
                    Icons.home_outlined,
                  ),
          ),
          BottomNavigationBarItem(
            icon: _page == 1
                ? const Icon(Icons.search, color: Pallete.blueColor)
                : const Icon(
                    Icons.search_outlined,
                  ),
          ),
          BottomNavigationBarItem(
            icon: _page == 2
                ? const Icon(Icons.notifications, color: Pallete.blueColor)
                : const Icon(
                    Icons.notifications_outlined,
                  ),
          ),
        ],
      ),
      drawer: const SideDrawer(),
    );
  }
}

burada AppBar'ın actions'ında bir şey yok önceden bir şey yazıp silmişim orayı kaldırabilirsiniz title kısmında svgpicture.asset yani bir svg picture'ına projede erişip gösteriyoruz kooginLogo denilen şey AssetConstants'tan gelen resim pathi yani twitter logosu buraya gelecek. SvgPicture kullanılması zorunlu değil centerTitle'ı true'ya çekin eğer macte yazmıyorsanız androidte otomatik en solda başlar appbar. body kısmına da IndexedStack kullanıyoruz ilk index 0 olacak _page'in ilk değeri 0 çünkü daha sonra bu indexedstack'in çocuklarına da bottomTabBarPages diye UIConstants'ın bottomTabBarPages kısmında gösterilecek sayfaları oraya tek tek veriyoruz sayfa1(), sayfa2(), sayfa3(), şeklinde verilirse 0.indexte sayfa1 1.indexte sayfa2 yer alır.

Kod:
  static const List<Widget> bottomTabBarPages = [
    PostList(),
    ExploreView(),
    NotificationView(),
  ];

orada bulunan şeyler aslında bunlar yani Postları listeleyecek olan bir sayfa, Arama sayfası (kullanıcıları aratabileceğimiz bir sayfa), Notificationview sayfası da bildirimler için (kullanıcı başka bir kullanıcının mesajını beğendiğinde burada .... beğendi şeklinde gözükecek bildirimlerin listelenmesi. Şu anda bu sayfalar boş olabilir önemli değil öncelikle indexedStack'i doğru kurmak önemli olan. Daha sonra bottomNavigationBar kısmına tabbar kullanıyoruz ama material design'dan gelen değil de cupertino'dan gelen aslında fark etmiyor pek istediğinizi kullanın. tıklanıldığında onPageChange metodunu çalıştıracak şu anki indexi de _page olacak dinamik şekilde çalışacak işte. Itemlarını da BottomNavigationBarItem olarak vermişiz bu şekilde tek tek vermek yerine 1 tane tanımlayıp da yapabilirdik seri seri geçelim. Örneğin page 2 ise bildirimler sayfasına gidileceği için oraya bildirim ikonu falan koyuyoruz ikon ile gidilecek sayfa bağdaşsın diye. Bu arada drawer da belirtmişiz onu şimdilik belirtmenize gerek yok ama isterseniz yapın.

Şimdi yapılacaklar

1- PostList'in yazılması
2- ExploreView'ın yazılması
3- NotificationView'ın yazılması

ama muhtemelen postlist'e başladıktan çok sonra exploreview ve diğer sayfayı yazacağız çünkü işin içine bir sürü şey girecek.

post_list.dart bu dosya bir widget olarak posts klasörünün içerisinde yer almalı her klasörümüzün controller, widgets ve views dosyaları mevcuttu yani widgets içerisine post_list.dart olmalı

Kod:
class PostList extends ConsumerStatefulWidget {
  const PostList({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _PostListState();
}

class _PostListState extends ConsumerState<PostList> {
  List<Post> renewedPosts = [];
  late bool firstOpening;

  void gettingRenewedPosts() async {
    final postController = ref.read(postControllerProvider.notifier);
    final renewPostLists = await postController.getPosts();
    setState(() {
      renewedPosts = renewPostLists;
    });
    firstOpening = false;
  }

burada PostList diye bir consumerstateful widget oluşturuyoruz yine riverpod package'ı kullanıyoruz yenilenen postlar şeklinde renewedPosts listesi oluşturuyoruz buradaki Post olarak belirtilen şey postlar için model sınıftan gelecek. firstOpening diye de bir ilk açılış olduğunda şunu yap ilk açılışı false'a çek gibisinden basit bir mantık kurduğumuz için bu şekilde bir değişken ve fonksiyon oluşturuyoruz. yenilenen postları getirme fonksiyonunun amacı şu aslında UI tarafında yukarıdan aşağıya refreshindicator kullanarak Twitter'ın ana sayfasındaki postları yenilemek için gelen dönen ok şeysini halletmek için yapıyoruz. Yenilendiği zaman yenilenen postları çekecek gibisinden. Henüz yazmadığımız post_controller.dart UI ile iletişim kuracak olan katmandı ona ihtiyacımız olacağı için postController'a bu şekilde ref.read diyerek erişiyoruz. ardından postController.getPosts diyerek controller katmanında yazacağımız olan postları bize vermeye yarayacak olan metodu çalıştırıyoruz o metod da post api'daki getPosts metodunu çalıştıracak. Ardından yenilenen renewedPosts listesinin görünürdeki widgetlara da yansıması için setState içerisinde belirtiyoruz. ve first Opening'i false a çekiyoruz artık sayfa ilk açılışta değil kullanıcı yenileme yaptı.

Kod:
@override
  void initState() {
    super.initState();
    firstOpening = true;
  }

başlangıçta firstOpening'i true yapıyoruz initState metodunda şimdi bu post_list.dart için biz bir provider oluşturacağız ve bu provider getPostsProvider isimli bir provider yani postları getirmeye yarayacak.

Kod:
final getPostsProvider = FutureProvider.autoDispose((ref) async {
  final postController = ref.watch(postControllerProvider.notifier);
  return postController.getPosts();
});

providers.dart klasörü içerisinde getPostsProvider'ı oluşturuyoruz bu FutureProvider olacak ve autoDispose'u kullanmanızı tavsiye ederim. Yapacağı şey postController'ı (yazdığımızı varsayıyoruz) tutmak ve onun getPosts metodunu çalıştırmak.

Buralara kadar yazınca postController'ı yazmadığınız için kırmızılıklar görmeniz normal şimdi postController'a gidelim.

post_controller.dart:

Burada bir sınıf oluşturuyoruz yine PostController'ı yazıyoruz şu an bu postController'ın ihtiyacı olan şey postAPI olacak çünkü asıl işleri yapan postAPI olacak zaten. Bu katman UI ile direkt iletişim kuracak UI ile API direkt iletişim kuramaz. StorageAPI bunu sonra açıklarım. Bildirimleri sonradan implemente ettiğimiz için şimdilik notificationController'ı buraya gereksinim olarak vermeye gerek yok ama yazarken zaten bir postu beğendiğimizde evet bunu çalıştırmak lazım gibisinden kendinizce bir mantık kurmuş olacaksınız. getPosts metodunu yazıyoruz ardından bu metod postAPI deki getPosts metodunu çalıştıracak asenkron bir işlem bekletiyoruz ama bunu ardından gelen postları mapleyerek (Post model sınıfından nesneler oluşturarak) oluşturulan bu nesneleri de toList diyerek bir listeye dönüştürüyoruz ve bunu döndürüyoruz sonuç olarak elimizde List<Post> gibi bir şey olacak yani ana sayfada gelecek olan postlar.

Kod:
class PostController extends StateNotifier<bool> {
  final PostAPI _postAPI;
  final StorageAPI _storageAPI;
  final Ref _ref;
  final NotificationController _notificationController;

  PostController({
    required Ref ref,
    required PostAPI postAPI,
    required StorageAPI storageAPI,
    required NotificationController notificationController,
  })  :  _ref = ref,
        _postAPI = postAPI,
        _storageAPI = storageAPI,
        _notificationController = notificationController,
        super(false);

  Future<List<Post>> getPosts() async {
    final postList = await _postAPI.getPosts();
    return postList.map((post) => Post.fromMap(post.data)).toList();
  }

Postları paylaşmak için sharePost metodunu oluşturuyoruz burası biraz karışık çünkü postları paylaşırken resimler paylaşabilme özelliği de var :D Bir de başka bir kullanıcının postuna da cevap olarak ayrı bir post paylaşılabildiği için karışık ilk olarak mantığımız şu eğer textin içerisinde bir şey yoksa snackbar gösterip text boş şeklinde bilgi veriyoruz eğer text boş değilse demek ki bir şeyler yazılmış bu sharePost metodunun kullanılacağı UI sayfası tahmin edeceğiniz üzere post paylaşmak için yazılması gereken ayrı bir sayfada tıklanacak olan Send butonunda kullanılacak

Kod:
void sharePost({
    required List<File> images,
    required String text,
    required BuildContext context,
    required String repliedTo,
    required String repliedToUserId,
  }) {
    if (text.isEmpty) {
      showSnackBar(context, AppTexts.plsEnter);
      return;
    }

    if (images.isNotEmpty) {
      _shareImagePost(
          images: images, text: text, context: context, repliedTo: repliedTo, repliedToUserId: repliedToUserId);
    } else {
      _shareTextPost(text: text, context: context, repliedTo: repliedTo, repliedToUserId: repliedToUserId);
    }
  }

Eğer resimli post ise shareImagePost metodunu çalıştır değilse shareTextPost metodunu çalıştır şeklinde iki ayrı metod olarak yazdığımız şeyleri kullanıyoruz.

Kod:
void _shareTextPost({
    required String text,
    required BuildContext context,
    required String repliedTo,
    required String repliedToUserId,
  }) async {
    state = true;
    final hashtags = _getHashtagsFromText(text);
    String link = _getLinkFromText(text);
    final user = _ref.read(currentUserDetailsProvider).value!;
    Post post = Post(
      text: text,
      hashtags: hashtags,
      link: link,
      imageLinks: const [],
      uid: user.uid,
      postType: PostType.text,
      postedAt: DateTime.now(),
      likes: const [],
      commentIds: const [],
      id: '',
      reshareCount: 0,
      retweetedBy: '',
      repliedTo: repliedTo,
    );
    final res = await _postAPI.sharePost(post);
    res.fold((l) => showSnackBar(context, l.message), (r) {
      if (repliedToUserId.isNotEmpty) {
        _notificationController.createNotification(
          text: '${user.name} ${AppTexts.repliedYourPost}',
          postId: r.$id,
          notificationType: NotificationType.reply,
          uid: repliedToUserId,
        );
      }
    });
    state = false;
  }

Yukarıda kullandığımız shareTextPost metodu öncelikle state'i true'ya çekip textlerdeki hashtag mantığı için TextSpan ile ayarlamıştık onun için ayrı bir metod yazmıştık onu kullanıyoruz şimdilik anlamanıza gerek yok bütün olunca anlaşılır şu anki kullanıcıları tutmaya yarayan bir provider kullanıyoruz özetle Post model sınıfından nesne oluşturuyoruz bu model sınıfı bir çok şey içeriyor sonuçta postun texti var beğeni sayısı var yorum atan kullanıcıların idlerini içeren bir liste var reshare sayısı var postedAt diye postun ne zaman paylaşıldığını içerecek olan bir sürü özellik var bu şekilde. Nesneyi oluşturduktan sonra bu nesneyi postAPI'nin sharePost metoduna yani asıl işi yapacak olan yere postu gönderiyoruz ardından fpdart package'ını tekrar kullanıyoruz. Notification kısmını sonradan ekledik eğer biri birine yorum atıyorsa notification oluşturma metodunu tetikliyor.

PostAPI tarafına gelecek olursak

Kod:
abstract class IPostAPI {
  FutureEither<Document> sharePost(Post post);
  Future<List<Document>> getPosts();
  Stream<RealtimeMessage> getLatestPost();
  FutureEither<Document> likePost(Post post);
  FutureEither<Document> updateReshareCount(Post post);
  Future<List<Document>> getRepliesToPost(Post post);
  Future<Document> getPostById(String id);
  Future<List<Document>> getUserPosts(String uid);
  Future<List<Document>> getPostsByHashtag(String hashtag);
}

benim belirlediğim Post api metodları bunlar şimdilik işimize yarayacak olanını gösterelim

sharePost zaten en önemlisi diğerleri birbirine benziyor


Kod:
class PostAPI implements IPostAPI {
  final Databases _db;
  final Realtime _realtime;

  PostAPI({required Databases db, required Realtime realtime})
      : _db = db,
        _realtime = realtime;

  @override
  FutureEither<Document> sharePost(post) async {
    try {
      final document = await _db.createDocument(
        databaseId: AppwriteConstants.databaseId,
        collectionId: AppwriteConstants.postCollection,
        documentId: ID.unique(),
        data: post.toMap(),
      );
      return right(document);
    } on AppwriteException catch (e, st) {
      return left(Failure(
        e.message ?? 'Some unexpected error occurred',
        st,
      ));
    } catch (e, st) {
      return left(Failure(
        e.toString(),
        st,
      ));
    }
  }

Classı oluşturduktan sonra Appwrite dan gelen dbye ihtiyacımız var ve aynı şekilde realtime'a ihtiyacımız var. onları constructorda aldıktan sonra fark ettiyseniz hep aynı şeyleri yapıyoruz. sharePost metodunu oluşturuyoruz controller'da çalıştırılan metod asıl işi yapan şey bu işte. db.createDocument metodunu çalıştırıyoruz (appwrite'ın yazmış olduğu metod) dbid collectionid documentId ve datayı istiyor bizden data aten post nesnesinin toMap metodu çünkü post model sınıfının toMap fonksiyonu var model sınıfları otomatik olarak yazdırabilirsinzi toMapi fromMapi falan otomatik yazdırılabilir. Eğer sıkıntı yoksa right (fpdart package'ından gelir) documentı döndür sıkıntı varsa o sıkıntıyı yazdır.

bir provider daha ekleyelim

Kod:
final userDetailsProvider = FutureProvider.family((ref, String? uid) {
  final authController = ref.watch(authControllerProvider.notifier);
  return authController.getUserData(uid!);
});

bir de post_card'ı UI olarak yazmak gerekli:

yapacağımız şey tek bir postu göstermek için kullanıcı adı solda avatar sağda yazı şeklinde altta da beğenme butonu falan. Öncelikle bu sınıf yine ConsumerWidget olacak ve post isteyecek bir kere zaten. Bir voidcallback kullanımı yapıyoruz ve yorum atılabilir mi bu posta şeklinde bir bool oluşturmuştum atılabilirse tıklandığında başka bir şey yapacağımız için o mantıkta bize lazım. Burası currentUser için oluşturulan providerı watchlayıp eğer currentUser null değilse userDetailsProvider'ı kullanıp eğer veri varsa gesturedetector kullanımı ile column veriyoruz columna row veriyoruz burada circleavatar kullanımı yapıyoruz solda kullanıcının profil fotoğrafı olacak. Devamında iconları veriyoruz eğer kullanıcı onaylı kullanıcı ise tik gösterimi olayı var arkasını doldurmadık daha.

Kod:
class PostCard extends ConsumerWidget {
  final Post post;
  final VoidCallback where;
  final bool commentability;
  const PostCard({required this.post, required this.commentability, super.key, required this.where});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final currentUser = ref.watch(currentUserDetailsProvider).value;
    final appThemeState = ref.watch(appThemeStateNotifier);
    return currentUser == null
        ? const SizedBox.shrink()
        : ref.watch(userDetailsProvider(post.uid)).when(
              data: (user) {
                return GestureDetector(
                  onTap: where,
                  child: Column(
                    children: [
                      Row(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            margin: const EdgeInsets.all(10),
                            child: GestureDetector(
                              onTap: () {
                                Navigator.push(context, UserProfileView.route(user));
                              },
                              child: CircleAvatar(
                                backgroundImage: NetworkImage(
                                    user.profilePic == '' ? AssetsConstants.noProfilePicture : user.profilePic),
                                radius: 25,
                              ),
                            ),
                          ),
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                if (post.retweetedBy.isNotEmpty)
                                  Row(
                                    children: [
                                      const Icon(
                                        Icons.subdirectory_arrow_left_outlined,
                                      ),
                                      const SizedBox(
                                        height: 2,
                                      ),
                                      Text(
                                        '${post.retweetedBy} ${AppTexts.reshared}',
                                        style: const TextStyle(
                                            color: Pallete.greyColor, fontSize: 17, fontWeight: FontWeight.w500),
                                      ),
                                    ],
                                  ),
                                Row(
                                  children: [
                                    Container(
                                      margin: EdgeInsets.only(right: user.isTwitterBlue ? 2 : 5),
                                      child: Text(
                                        user.name,
                                        style: const TextStyle(
                                          fontWeight: FontWeight.bold,
                                          fontSize: 16,
                                        ),
                                      ),
                                    ),
                                    if (user.isTwitterBlue)
                                      const Icon(
                                        Icons.verified,
                                        color: Pallete.blueColor,
                                      ),
                                    const SizedBox(
                                      width: 5,
                                    ),
                                    Text(
                                      '@${user.name} . ${timeago.format(post.postedAt, locale: 'en_short')}',
                                      style: const TextStyle(
                                        fontSize: 17,
                                        color: Pallete.greyColor,
                                      ),
                                    ),
                                  ],
                                ),
                                appThemeState.isDarkModeEnabled
                                    ? HashtagText(text: post.text, textColor: Pallete.whiteColor)
                                    : HashtagText(
                                        text: post.text,
                                        textColor: Pallete.blackColor,
                                      ),
                                if (post.postType == PostType.image) Padding(
                                  padding: const EdgeInsets.only(right: 10),
                                  child: CarouselImage(imageLinks: post.imageLinks),
                                ),
                                if (post.link.isNotEmpty) ...[
                                  const SizedBox(
                                    height: 4,
                                  ),
                                  AnyLinkPreview(
                                      displayDirection: UIDirection.uiDirectionHorizontal, link: 'https://${post.link}')
                                ],
                                Container(
                                  margin: const EdgeInsets.only(
                                    top: 10,
                                    right: 20,
                                  ),
                                  child: Row(
                                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                                    children: [
                                      if (commentability)
                                        PostIconButton(
                                          theIcon: Icons.mode_comment_outlined,
                                          text: '',
                                          onTap: () {
                                            Navigator.push(
                                              context,
                                              PostReplyScreen.route(post),
                                            );
                                          },
                                        ),
                                      LikeButton( //bunun için package kullandık 
                                        isLiked: post.likes.contains(currentUser.uid),
                                        onTap: (isLiked) async {
                                          ref.read(postControllerProvider.notifier).likePost(post, currentUser); //eğer tıklanırsa likePost metodunu çalıştıracak
                                          return !isLiked;
                                        },
                                        size: 25,
                                        likeCount: post.likes.length.toInt(),
                                        countBuilder: (likeCount, isLiked, text) {
                                          return Padding(
                                            padding: const EdgeInsets.only(left: 2),
                                            child: Text(
                                              text,
                                              style: TextStyle(
                                                color: isLiked ? Pallete.redColor : Pallete.whiteColor,
                                                fontSize: 16,
                                              ),
                                            ),
                                          );
                                        },
                                        likeBuilder: (isLiked) {
                                          return isLiked
                                              ? const Icon(Icons.favorite, color: Pallete.redColor)
                                              : const Icon(
                                                  Icons.favorite_border_outlined,
                                                  color: Pallete.greyColor,
                                                );
                                        },
                                      ),

                                      //cok begenmedim bu resharei ama devam

                                      PostIconButton(
                                        theIcon: Icons.subdirectory_arrow_left_outlined,
                                        text: '',
                                        onTap: () {
                                          ref
                                              .read(postControllerProvider.notifier)
                                              .resharePost(post, currentUser, context); //tıklandığında resharepost metodunu çalıştıracak controller ile iletişim kuruyoruz işte
                                        },
                                      ),

                                     
                                    ],
                                  ),
                                ),
                                const SizedBox(
                                  height: 1,
                                ),
                              ],
                            ),
                          )
                        ],
                      ),
                      const Divider(
                        color: Pallete.greyColor,
                        thickness: 0.1,
                      ),
                    ],
                  ),
                );
              },
              error: (error, stackTrace) => ErrorText(
                error: error.toString(),
              ),
              loading: () => const Loader(),
            );
  }
}

post_list.dart içerisinde artık post cardlarını döndürebiliriz:

Kod:
@override
  Widget build(BuildContext context) {
    final appThemeState = ref.watch(appThemeStateNotifier);
    return ref.watch(getPostsProvider).when(
          data: (posts) {
            return Column(
              children: [
                Expanded(
                  child: RefreshIndicator(
                    color: appThemeState.isDarkModeEnabled ? Pallete.whiteColor : Pallete.blueColor,
                    onRefresh: () async {
                      setState(
                        () {
                          gettingRenewedPosts();
                        },
                      );

                    },
                    child: ListView.builder(
                      itemCount: firstOpening ? posts.length : renewedPosts.length,
                      itemBuilder: (context, index) {
                        final post = firstOpening ? posts[index] : renewedPosts[index];
                        return PostCard(
                          post: post,
                          commentability: true,
                          where: () {
                            Navigator.push(
                              context,
                              PostReplyScreen.route(post),
                            );
                          },
                        );
                      },
                    ),
                  ),
                ),
              ],
            );
            /*
            
             */
          },
          error: (error, stackTrace) => ErrorText(error: error.toString()),
          loading: () => const Loader(),
        );
  }
}

Postların gözükme şekli:


fe5sPI.png


Devamında yapılacakları yazıp unutmayalım
- post oluşturma ekranının yazılması (göstermek için appwrite dan dummy postları eklemiştim uygulama içinden yazıp gösterelim)
- diğer eksik metodların eklenmesi

Devamında görüşelim.
Gauloran <3​
 

Ogehan

-
5 Haz 2016
2,135
238
</>
Ellerinize sağlık hocam bilgilendirici ve ayrıca öğretici bir proje emeğinize sağlık.
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.