✨ Notifications
This commit is contained in:
		@@ -41,7 +41,6 @@ android {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    defaultConfig {
 | 
					    defaultConfig {
 | 
				
			||||||
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
 | 
					 | 
				
			||||||
        applicationId "studio.smartsheep.goatagent"
 | 
					        applicationId "studio.smartsheep.goatagent"
 | 
				
			||||||
        // You can update the following values to match your application needs.
 | 
					        // You can update the following values to match your application needs.
 | 
				
			||||||
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
 | 
					        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
 | 
				
			||||||
@@ -49,6 +48,10 @@ android {
 | 
				
			|||||||
        targetSdkVersion flutter.targetSdkVersion
 | 
					        targetSdkVersion flutter.targetSdkVersion
 | 
				
			||||||
        versionCode flutterVersionCode.toInteger()
 | 
					        versionCode flutterVersionCode.toInteger()
 | 
				
			||||||
        versionName flutterVersionName
 | 
					        versionName flutterVersionName
 | 
				
			||||||
 | 
					        manifestPlaceholders = [
 | 
				
			||||||
 | 
					                applicationName      : 'android.app.Application',
 | 
				
			||||||
 | 
					                appAuthRedirectScheme: 'goatagent'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    buildTypes {
 | 
					    buildTypes {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					// Generated file.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If you wish to remove Flutter's multidex support, delete this entire file.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Modifications to this file should be done in a copy under a different name
 | 
				
			||||||
 | 
					// as this file may be regenerated.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package io.flutter.app;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Application;
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import androidx.annotation.CallSuper;
 | 
				
			||||||
 | 
					import androidx.multidex.MultiDex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Extension of {@link android.app.Application}, adding multidex support.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class FlutterMultiDexApplication extends Application {
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  @CallSuper
 | 
				
			||||||
 | 
					  protected void attachBaseContext(Context base) {
 | 
				
			||||||
 | 
					    super.attachBaseContext(base);
 | 
				
			||||||
 | 
					    MultiDex.install(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,12 +3,15 @@ import 'dart:io';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
 | 
					import 'package:flutter_secure_storage/flutter_secure_storage.dart';
 | 
				
			||||||
 | 
					import 'package:goatagent/firebase.dart';
 | 
				
			||||||
import 'package:goatagent/screens/auth.dart';
 | 
					import 'package:goatagent/screens/auth.dart';
 | 
				
			||||||
import 'package:oauth2/oauth2.dart' as oauth2;
 | 
					import 'package:oauth2/oauth2.dart' as oauth2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AuthGuard {
 | 
					class AuthGuard {
 | 
				
			||||||
  static final AuthGuard _singleton = AuthGuard._internal();
 | 
					  static final AuthGuard _singleton = AuthGuard._internal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final deviceEndpoint =
 | 
				
			||||||
 | 
					      Uri.parse('https://id.smartsheep.studio/api/notifications/subscribe');
 | 
				
			||||||
  final authorizationEndpoint =
 | 
					  final authorizationEndpoint =
 | 
				
			||||||
      Uri.parse('https://id.smartsheep.studio/auth/o/connect');
 | 
					      Uri.parse('https://id.smartsheep.studio/auth/o/connect');
 | 
				
			||||||
  final tokenEndpoint =
 | 
					  final tokenEndpoint =
 | 
				
			||||||
@@ -32,12 +35,17 @@ class AuthGuard {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Future<bool> pickClient() async {
 | 
					  Future<bool> pickClient() async {
 | 
				
			||||||
    if (await storage.containsKey(key: storageKey)) {
 | 
					    if (await storage.containsKey(key: storageKey)) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
        var credentials =
 | 
					        var credentials =
 | 
				
			||||||
            oauth2.Credentials.fromJson((await storage.read(key: storageKey))!);
 | 
					            oauth2.Credentials.fromJson((await storage.read(key: storageKey))!);
 | 
				
			||||||
        client = oauth2.Client(credentials,
 | 
					        client = oauth2.Client(credentials,
 | 
				
			||||||
            identifier: clientId, secret: clientSecret);
 | 
					            identifier: clientId, secret: clientSecret);
 | 
				
			||||||
      print(await storage.readAll());
 | 
					        await pullProfiles();
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        logout();
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -78,18 +86,46 @@ class AuthGuard {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> pullProfiles() async {
 | 
				
			||||||
 | 
					    if (client != null) {
 | 
				
			||||||
 | 
					      var userinfo = await client!.get(userinfoEndpoint);
 | 
				
			||||||
 | 
					      storage.write(key: profileKey, value: utf8.decode(userinfo.bodyBytes));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> login(BuildContext context) async {
 | 
					  Future<void> login(BuildContext context) async {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      client = await createClient(context);
 | 
					      client = await createClient(context);
 | 
				
			||||||
      var userinfo = await client!.read(userinfoEndpoint);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      storage.write(key: profileKey, value: userinfo);
 | 
					 | 
				
			||||||
      storage.write(key: storageKey, value: client!.credentials.toJson());
 | 
					      storage.write(key: storageKey, value: client!.credentials.toJson());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await pullProfiles();
 | 
				
			||||||
 | 
					      await subscribeNotify();
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      print(e);
 | 
					      print(e);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> subscribeNotify() async {
 | 
				
			||||||
 | 
					    if (client == null) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var token = await initializeFirebaseMessaging();
 | 
				
			||||||
 | 
					    if (token == null) {
 | 
				
			||||||
 | 
					      print("failed to initialize firebase messaging...");
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var response = await client!.post(
 | 
				
			||||||
 | 
					      deviceEndpoint,
 | 
				
			||||||
 | 
					      headers: {"Content-Type": "application/json"},
 | 
				
			||||||
 | 
					      body: jsonEncode({"device_id": token, "provider": "firebase"}),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (response.statusCode != 200) {
 | 
				
			||||||
 | 
					      print(response.body);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void logout() {
 | 
					  void logout() {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      storage.delete(key: profileKey);
 | 
					      storage.delete(key: profileKey);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,16 @@ void initializeFirebase() async {
 | 
				
			|||||||
    options: DefaultFirebaseOptions.currentPlatform,
 | 
					    options: DefaultFirebaseOptions.currentPlatform,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await FirebaseMessaging.instance.requestPermission(
 | 
				
			||||||
 | 
					    alert: true,
 | 
				
			||||||
 | 
					    announcement: false,
 | 
				
			||||||
 | 
					    badge: true,
 | 
				
			||||||
 | 
					    carPlay: false,
 | 
				
			||||||
 | 
					    criticalAlert: false,
 | 
				
			||||||
 | 
					    provisional: false,
 | 
				
			||||||
 | 
					    sound: true,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  FlutterError.onError = (errorDetails) {
 | 
					  FlutterError.onError = (errorDetails) {
 | 
				
			||||||
    FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
 | 
					    FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:firebase_messaging/firebase_messaging.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,13 +12,17 @@ class AgentNavigation extends StatefulWidget {
 | 
				
			|||||||
      icon: Icon(Icons.home),
 | 
					      icon: Icon(Icons.home),
 | 
				
			||||||
      label: 'Dashboard',
 | 
					      label: 'Dashboard',
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    NavigationDestination(
 | 
				
			||||||
 | 
					      icon: Icon(Icons.notifications),
 | 
				
			||||||
 | 
					      label: 'Notifications',
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    NavigationDestination(
 | 
					    NavigationDestination(
 | 
				
			||||||
      icon: Icon(Icons.account_circle),
 | 
					      icon: Icon(Icons.account_circle),
 | 
				
			||||||
      label: 'Account',
 | 
					      label: 'Account',
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static const destinations = ["/", "/account"];
 | 
					  static const destinations = ["/", "/notifications", "/account"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<AgentNavigation> createState() => _AgentNavigationState();
 | 
					  State<AgentNavigation> createState() => _AgentNavigationState();
 | 
				
			||||||
@@ -26,6 +31,32 @@ class AgentNavigation extends StatefulWidget {
 | 
				
			|||||||
class _AgentNavigationState extends State<AgentNavigation> {
 | 
					class _AgentNavigationState extends State<AgentNavigation> {
 | 
				
			||||||
  int _selected = 0;
 | 
					  int _selected = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> initMessage(BuildContext context) async {
 | 
				
			||||||
 | 
					    void navigate() {
 | 
				
			||||||
 | 
					      widget.router.push("/notifications");
 | 
				
			||||||
 | 
					      setState(() {
 | 
				
			||||||
 | 
					        _selected = 1;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    RemoteMessage? initialMessage =
 | 
				
			||||||
 | 
					    await FirebaseMessaging.instance.getInitialMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (initialMessage != null) {
 | 
				
			||||||
 | 
					      navigate();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FirebaseMessaging.onMessageOpenedApp.listen((event) {
 | 
				
			||||||
 | 
					      navigate();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    initMessage(context);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return NavigationBar(
 | 
					    return NavigationBar(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,11 @@
 | 
				
			|||||||
 | 
					import 'package:firebase_messaging/firebase_messaging.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
import 'package:goatagent/auth.dart';
 | 
					import 'package:goatagent/auth.dart';
 | 
				
			||||||
import 'package:goatagent/screens/auth.dart';
 | 
					 | 
				
			||||||
import 'package:goatagent/firebase.dart';
 | 
					import 'package:goatagent/firebase.dart';
 | 
				
			||||||
import 'package:goatagent/screens/account.dart';
 | 
					import 'package:goatagent/screens/account.dart';
 | 
				
			||||||
import 'package:goatagent/screens/dashboard.dart';
 | 
					import 'package:goatagent/screens/dashboard.dart';
 | 
				
			||||||
 | 
					import 'package:goatagent/screens/notifications.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'layouts/navigation.dart';
 | 
					import 'layouts/navigation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,9 +22,18 @@ class GoatAgent extends StatelessWidget {
 | 
				
			|||||||
  final _router = GoRouter(
 | 
					  final _router = GoRouter(
 | 
				
			||||||
    navigatorKey: GlobalKey<NavigatorState>(),
 | 
					    navigatorKey: GlobalKey<NavigatorState>(),
 | 
				
			||||||
    routes: [
 | 
					    routes: [
 | 
				
			||||||
      GoRoute(path: '/', builder: (context, state) => const DashboardScreen()),
 | 
					 | 
				
			||||||
      GoRoute(
 | 
					      GoRoute(
 | 
				
			||||||
          path: '/account', builder: (context, state) => const AccountScreen())
 | 
					        path: '/',
 | 
				
			||||||
 | 
					        builder: (context, state) => const DashboardScreen(),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      GoRoute(
 | 
				
			||||||
 | 
					        path: '/notifications',
 | 
				
			||||||
 | 
					        builder: (context, state) => const NotificationScreen(),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      GoRoute(
 | 
				
			||||||
 | 
					        path: '/account',
 | 
				
			||||||
 | 
					        builder: (context, state) => const AccountScreen(),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,7 +55,8 @@ class GoatAgent extends StatelessWidget {
 | 
				
			|||||||
              body: child,
 | 
					              body: child,
 | 
				
			||||||
              // bottomNavigationBar: const AgentBottomNavigation()
 | 
					              // bottomNavigationBar: const AgentBottomNavigation()
 | 
				
			||||||
              bottomNavigationBar: AgentNavigation(router: _router),
 | 
					              bottomNavigationBar: AgentNavigation(router: _router),
 | 
				
			||||||
                  ))
 | 
					            ),
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,9 @@ class _AccountScreenState extends State<AccountScreen> {
 | 
				
			|||||||
          padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
 | 
					          padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
 | 
				
			||||||
          child: Column(
 | 
					          child: Column(
 | 
				
			||||||
            children: [
 | 
					            children: [
 | 
				
			||||||
              NameCard(
 | 
					              Padding(
 | 
				
			||||||
 | 
					                padding: const EdgeInsets.only(top: 20),
 | 
				
			||||||
 | 
					                child: NameCard(
 | 
				
			||||||
                  onLogin: () async {
 | 
					                  onLogin: () async {
 | 
				
			||||||
                    await AuthGuard().login(context);
 | 
					                    await AuthGuard().login(context);
 | 
				
			||||||
                    var authorized = await AuthGuard().isAuthorized();
 | 
					                    var authorized = await AuthGuard().isAuthorized();
 | 
				
			||||||
@@ -39,6 +41,7 @@ class _AccountScreenState extends State<AccountScreen> {
 | 
				
			|||||||
                    });
 | 
					                    });
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
              FutureBuilder(
 | 
					              FutureBuilder(
 | 
				
			||||||
                future: AuthGuard().isAuthorized(),
 | 
					                future: AuthGuard().isAuthorized(),
 | 
				
			||||||
                builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
 | 
					                builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										96
									
								
								lib/screens/notifications.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								lib/screens/notifications.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					import 'dart:convert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:goatagent/auth.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationScreen extends StatefulWidget {
 | 
				
			||||||
 | 
					  const NotificationScreen({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<NotificationScreen> createState() => _NotificationScreenState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _NotificationScreenState extends State<NotificationScreen> {
 | 
				
			||||||
 | 
					  final notificationEndpoint = Uri.parse(
 | 
				
			||||||
 | 
					      'https://id.smartsheep.studio/api/notifications?skip=0&take=20');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  List<dynamic> notifications = List.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    _pullNotifications();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _pullNotifications() async {
 | 
				
			||||||
 | 
					    if (await AuthGuard().isAuthorized()) {
 | 
				
			||||||
 | 
					      await AuthGuard().pullProfiles();
 | 
				
			||||||
 | 
					      var profiles = await AuthGuard().readProfiles();
 | 
				
			||||||
 | 
					      setState(() {
 | 
				
			||||||
 | 
					        notifications = profiles['notifications'];
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _markAsRead(element) async {
 | 
				
			||||||
 | 
					    if (AuthGuard().client != null) {
 | 
				
			||||||
 | 
					      var id = element['id'];
 | 
				
			||||||
 | 
					      var uri =
 | 
				
			||||||
 | 
					          Uri.parse('https://id.smartsheep.studio/api/notifications/$id/read');
 | 
				
			||||||
 | 
					      await AuthGuard().client!.put(uri);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      body: SafeArea(
 | 
				
			||||||
 | 
					        child: Padding(
 | 
				
			||||||
 | 
					          padding: const EdgeInsets.only(left: 20, right: 20, top: 30),
 | 
				
			||||||
 | 
					          child: RefreshIndicator(
 | 
				
			||||||
 | 
					            onRefresh: _pullNotifications,
 | 
				
			||||||
 | 
					            child: CustomScrollView(
 | 
				
			||||||
 | 
					              slivers: [
 | 
				
			||||||
 | 
					                notifications.isEmpty
 | 
				
			||||||
 | 
					                    ? const SliverToBoxAdapter(
 | 
				
			||||||
 | 
					                        child: Card(
 | 
				
			||||||
 | 
					                          child: Padding(
 | 
				
			||||||
 | 
					                            padding: EdgeInsets.all(10),
 | 
				
			||||||
 | 
					                            child: ListTile(
 | 
				
			||||||
 | 
					                              leading: Icon(Icons.check),
 | 
				
			||||||
 | 
					                              title: Text('You\'re done!'),
 | 
				
			||||||
 | 
					                              subtitle: Text(
 | 
				
			||||||
 | 
					                                  'There are no notifications unread for you.'),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                    : SliverList.builder(
 | 
				
			||||||
 | 
					                        itemCount: notifications.length,
 | 
				
			||||||
 | 
					                        itemBuilder: (BuildContext context, int index) {
 | 
				
			||||||
 | 
					                          var element = notifications[index];
 | 
				
			||||||
 | 
					                          return Dismissible(
 | 
				
			||||||
 | 
					                              key: Key('notification-$index'),
 | 
				
			||||||
 | 
					                              onDismissed: (direction) {
 | 
				
			||||||
 | 
					                                var subject = element["subject"];
 | 
				
			||||||
 | 
					                                _markAsRead(element).then((value) {
 | 
				
			||||||
 | 
					                                  ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
 | 
					                                      SnackBar(
 | 
				
			||||||
 | 
					                                          content:
 | 
				
			||||||
 | 
					                                              Text('「$subject」 mark as read')));
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                              },
 | 
				
			||||||
 | 
					                              child: ListTile(
 | 
				
			||||||
 | 
					                                title: Text(element["subject"]),
 | 
				
			||||||
 | 
					                                subtitle: Text(element["content"]),
 | 
				
			||||||
 | 
					                              ));
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user