Skip to content

Commit

Permalink
Merge pull request #150 from K-w-e/feature/notification_system
Browse files Browse the repository at this point in the history
WIP notifications system
  • Loading branch information
mikev-cw authored Mar 25, 2024
2 parents 184c848 + 389eb68 commit 5f30d1a
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 26 deletions.
16 changes: 13 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:workmanager/workmanager.dart';
import 'package:sossoldi/utils/worker_manager.dart';

import 'pages/notifications/notifications_service.dart';
import 'providers/theme_provider.dart';
import 'routes.dart';
import 'utils/app_theme.dart';

void main() {
initializeDateFormatting('it_IT', null)
.then((_) => runApp(const ProviderScope(child: Launcher())));
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if(Platform.isAndroid){
requestNotificationPermissions();
initializeNotifications();
Workmanager().initialize(callbackDispatcher);
}
initializeDateFormatting('it_IT', null).then((_) => runApp(const ProviderScope(child: Launcher())));
}

class Launcher extends ConsumerWidget {
Expand Down
22 changes: 22 additions & 0 deletions lib/pages/notifications/notifications_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';

final FlutterLocalNotificationsPlugin notificationsPlugin = FlutterLocalNotificationsPlugin();

const AndroidNotificationChannel channel = AndroidNotificationChannel(
'sossoldi#reminder', // id del canale
'Reminder', // nome del canale
importance: Importance.high,
);

void initializeNotifications() async {
var initializationSettingsAndroid = const AndroidInitializationSettings('@mipmap/ic_launcher');
var initializationSettingsIOS = const DarwinInitializationSettings();
var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
notificationsPlugin.initialize(initializationSettings);
await notificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
}

void requestNotificationPermissions() async {
await Permission.notification.request();
}
76 changes: 61 additions & 15 deletions lib/pages/notifications/notifications_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../constants/functions.dart';
import '../../constants/style.dart';
import '../../providers/settings_provider.dart';
import '../../utils/worker_manager.dart';
import 'widgets/NotificationTypeTile.dart';

class NotificationsSettings extends ConsumerStatefulWidget {
const NotificationsSettings({super.key});

@override
ConsumerState<NotificationsSettings> createState() => _NotificationsSettingsState();
ConsumerState<NotificationsSettings> createState() =>
_NotificationsSettingsState();
}

class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> with Functions {
class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> {

void setNotificationTypeCallback(NotificationReminderType type) {
setState(() {
ref.watch(transactionReminderCadenceProvider.notifier).state = type;
ref.read(settingsProvider.notifier).updateNotifications();
scheduleTransactionReminder(type);
});
}

@override
Widget build(BuildContext context) {
// Is used to init the state of the switches
ref.read(settingsProvider);
final isReminderEnabled = ref.watch(transactionReminderSwitchProvider);
final notificationSettings = ref.watch(settingsProvider);

return Scaffold(
appBar: AppBar(
leading: IconButton(
Expand All @@ -30,7 +42,8 @@ class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> w
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
padding:
const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Row(
children: [
Container(
Expand Down Expand Up @@ -74,21 +87,41 @@ class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> w
),
),
CupertinoSwitch(
value: ref.watch(transactionReminderSwitchProvider),
value: isReminderEnabled,
onChanged: (value) {
ref.read(transactionReminderSwitchProvider.notifier).state = value;
ref
.read(transactionReminderSwitchProvider.notifier)
.state = value;
ref.read(settingsProvider.notifier).updateNotifications();
toggleTransactionReminder(value);
},
),
],
),
),
AnimatedCrossFade(
crossFadeState: isReminderEnabled
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 150),
firstChild: Container(),
secondChild: Column(
children: [
NotificationTypeTile(type: NotificationReminderType.daily, setNotificationTypeCallback: () => setNotificationTypeCallback(NotificationReminderType.daily)),
NotificationTypeTile(type: NotificationReminderType.weekly, setNotificationTypeCallback: () => setNotificationTypeCallback(NotificationReminderType.weekly)),
NotificationTypeTile(type: NotificationReminderType.monthly, setNotificationTypeCallback: () => setNotificationTypeCallback(NotificationReminderType.monthly))
],
),
),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 28, top: 24, bottom: 8),
child: Text(
"RECURRING TRANSACTIONS",
style: Theme.of(context).textTheme.labelLarge!.copyWith(color: grey1),
style: Theme.of(context)
.textTheme
.labelLarge!
.copyWith(color: grey1),
),
),
Container(
Expand All @@ -113,16 +146,21 @@ class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> w
CupertinoSwitch(
value: ref.watch(transactionRecAddedSwitchProvider),
onChanged: (value) {
ref.read(transactionRecAddedSwitchProvider.notifier).state = value;
ref.read(settingsProvider.notifier).updateNotifications();
ref
.read(transactionRecAddedSwitchProvider.notifier)
.state = value;
ref
.read(settingsProvider.notifier)
.updateNotifications();
},
),
],
),
const SizedBox(height: 12),
Divider(
height: 1,
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
color:
Theme.of(context).colorScheme.primary.withOpacity(0.4),
),
const SizedBox(height: 12),
Row(
Expand All @@ -137,8 +175,13 @@ class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> w
CupertinoSwitch(
value: ref.watch(transactionRecReminderSwitchProvider),
onChanged: (value) {
ref.read(transactionRecReminderSwitchProvider.notifier).state = value;
ref.read(settingsProvider.notifier).updateNotifications();
ref
.read(
transactionRecReminderSwitchProvider.notifier)
.state = value;
ref
.read(settingsProvider.notifier)
.updateNotifications();
},
),
],
Expand All @@ -151,7 +194,10 @@ class _NotificationsSettingsState extends ConsumerState<NotificationsSettings> w
padding: const EdgeInsets.only(left: 28, top: 6),
child: Text(
"Remind me before a recurring transaction is added",
style: Theme.of(context).textTheme.labelMedium!.copyWith(color: grey1),
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: grey1),
),
),
],
Expand Down
89 changes: 89 additions & 0 deletions lib/pages/notifications/widgets/NotificationTypeTile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../../../providers/settings_provider.dart';

class NotificationTypeTile extends ConsumerStatefulWidget {
final NotificationReminderType type;
final VoidCallback setNotificationTypeCallback;

const NotificationTypeTile(
{Key? key, required this.type, required this.setNotificationTypeCallback})
: super(key: key);

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

class _NotificationTypeTileState extends ConsumerState<NotificationTypeTile> {
late SharedPreferences prefs;
bool isPrefInizialized = false;

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

Future<void> initPrefs() async {
prefs = await SharedPreferences.getInstance();
setState(() {
isPrefInizialized = true;
});
}

@override
Widget build(BuildContext context) {
if (!isPrefInizialized) return Container();

NotificationReminderType? notificationReminderType =
NotificationReminderType?.values?.firstWhere((e) =>
e.toString().split('.').last ==
prefs.getString('transaction-reminder-cadence'));

final typeName =
"${widget.type.name[0].toUpperCase()}${widget.type.name.substring(1).toLowerCase()}";

return GestureDetector(
onTap: () {
widget.setNotificationTypeCallback.call();
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
width: MediaQuery.of(context).size.width,
height: 40,
decoration: BoxDecoration(
border: Border.all(
color: notificationReminderType == widget.type
? Colors.blue
: Colors.grey,
),
borderRadius: BorderRadius.circular(4.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text(
typeName,
style: TextStyle(
color: notificationReminderType == widget.type
? Colors.blue
: Colors.black,
),
),
const Spacer(),
notificationReminderType == widget.type
? const Icon(
Icons.check,
color: Colors.blue,
)
: Container()
],
),
),
),
);
}
}
16 changes: 10 additions & 6 deletions lib/pages/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

// ignore_for_file: unused_result

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sossoldi/providers/statistics_provider.dart';
Expand All @@ -23,7 +25,7 @@ class SettingsPage extends ConsumerStatefulWidget {
ConsumerState<SettingsPage> createState() => _SettingsPageState();
}

var settingsOptions = const [
var settingsOptions = [
[
Icons.settings,
"General Settings",
Expand Down Expand Up @@ -58,7 +60,7 @@ var settingsOptions = const [
Icons.notifications_active,
"Notifications",
"Manage your notifications settings",
"/notifications-settings",
Platform.isAndroid ? "/notifications-settings" : null,
],
[
Icons.info,
Expand Down Expand Up @@ -109,14 +111,16 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
],
),
),
ListView.separated(
ListView.builder(
itemCount: settingsOptions.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => const SizedBox(height: 16),
itemBuilder: (context, i) {
List setting = settingsOptions[i];
return DefaultCard(
if (setting[3] == null) return Container();
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: DefaultCard(
onTap: () {
if (setting[3] != null) {
Navigator.of(context).pushNamed(setting[3] as String);
Expand Down Expand Up @@ -163,7 +167,7 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
),
],
),
);
));
},
),
],
Expand Down
13 changes: 11 additions & 2 deletions lib/providers/settings_provider.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

enum NotificationReminderType {
none,
daily,
weekly,
monthly,
}

final transactionReminderSwitchProvider = StateProvider<bool>((ref) => false);
final transactionReminderCadenceProvider = StateProvider<NotificationReminderType>((ref) => NotificationReminderType.none);
final transactionRecReminderSwitchProvider = StateProvider<bool>((ref) => false);
final transactionRecAddedSwitchProvider = StateProvider<bool>((ref) => false);

Expand All @@ -14,6 +22,7 @@ class AsyncSettingsNotifier extends AsyncNotifier<dynamic> {
Future<void> _getSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
ref.read(transactionReminderSwitchProvider.notifier).state = prefs.getBool('transaction-reminder') ?? false;
ref.read(transactionReminderCadenceProvider.notifier).state = prefs.getString('transaction-reminder-cadence') as NotificationReminderType;
ref.read(transactionRecReminderSwitchProvider.notifier).state = prefs.getBool('transaction-rec-reminder') ?? false;
ref.read(transactionRecAddedSwitchProvider.notifier).state = prefs.getBool('transaction-rec-added') ?? false;
}
Expand All @@ -23,8 +32,8 @@ class AsyncSettingsNotifier extends AsyncNotifier<dynamic> {
state = await AsyncValue.guard(() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('transaction-reminder', ref.read(transactionReminderSwitchProvider));
await prefs.setBool(
'transaction-rec-reminder', ref.read(transactionRecReminderSwitchProvider));
await prefs.setString('transaction-reminder-cadence', ref.read(transactionReminderCadenceProvider).name);
await prefs.setBool('transaction-rec-reminder', ref.read(transactionRecReminderSwitchProvider));
await prefs.setBool('transaction-rec-added', ref.read(transactionRecAddedSwitchProvider));

return _getSettings();
Expand Down
Loading

0 comments on commit 5f30d1a

Please sign in to comment.