Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding notifications #135

Merged
merged 20 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

<application
android:label="Infinite Horizons"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">

<activity
android:name=".MainActivity"
android:exported="true"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added android/app/src/main/res/raw/break_ended.m4a
Binary file not shown.
Binary file not shown.
12 changes: 12 additions & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,16 @@ post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
################ Awesome Notifications pod modification 1 ###################
awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
require awesome_pod_file
update_awesome_pod_build_settings(installer)
################ Awesome Notifications pod modification 1 ###################
end

################ Awesome Notifications pod modification 2 ###################
awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
require awesome_pod_file
update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root)
################ Awesome Notifications pod modification 2 ###################

14 changes: 14 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
6482FAA52ED6EDE8E8436926 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F80BBDF2B702494CC7FA307 /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
976B225A2C26CD0500DD4ED6 /* break_ended.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 976B22582C26CD0500DD4ED6 /* break_ended.aiff */; };
976B225B2C26CD0500DD4ED6 /* session_completed.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 976B22592C26CD0500DD4ED6 /* session_completed.aiff */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
Expand Down Expand Up @@ -57,6 +59,8 @@
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
976B22582C26CD0500DD4ED6 /* break_ended.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = break_ended.aiff; path = ../../../../Downloads/files/break_ended.aiff; sourceTree = "<group>"; };
976B22592C26CD0500DD4ED6 /* session_completed.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = session_completed.aiff; path = ../../../../Downloads/files/session_completed.aiff; sourceTree = "<group>"; };
guyluz11 marked this conversation as resolved.
Show resolved Hide resolved
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -118,6 +122,8 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
976B22582C26CD0500DD4ED6 /* break_ended.aiff */,
976B22592C26CD0500DD4ED6 /* session_completed.aiff */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
Expand Down Expand Up @@ -259,10 +265,12 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
976B225B2C26CD0500DD4ED6 /* session_completed.aiff in Resources */,
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
976B225A2C26CD0500DD4ED6 /* break_ended.aiff in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -468,7 +476,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
Expand Down Expand Up @@ -662,7 +672,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
Expand Down Expand Up @@ -690,7 +702,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
Expand Down
5 changes: 5 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
</array>
guyluz11 marked this conversation as resolved.
Show resolved Hide resolved
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
Empty file added ios/Runner/break_ended.aiff
Empty file.
Empty file.
22 changes: 16 additions & 6 deletions lib/domain/timer_states.dart → lib/domain/energy_level.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class TimerStates {
TimerStates(this.type, this.sessions);
class EnergyLevel {
EnergyLevel(this.type, this.sessions);

factory TimerStates.fromEnergyType(EnergyType type) {
factory EnergyLevel.fromEnergyType(EnergyType type) {
List<TimerSession> states;

switch (type) {
Expand Down Expand Up @@ -34,7 +34,7 @@ class TimerStates {
];
}

return TimerStates(type, states);
return EnergyLevel(type, states);
}

EnergyType type;
Expand All @@ -49,7 +49,7 @@ class TimerStates {
currentState = 0;
return;
}
currentState = currentState + 1;
currentState++;
}
}

Expand All @@ -58,14 +58,15 @@ class TimerSession {

Duration study;
Duration breakDuration;
final Duration getReadyForBreak = const Duration(seconds: 10);
}

enum EnergyType {
undefined(
'undefined',
Duration.zero,
),
veryLow('very_low', Duration(minutes: 5)),
veryLow('very_low', Duration(seconds: 30)),
guyluz11 marked this conversation as resolved.
Show resolved Hide resolved
low('low', Duration(minutes: 10)),
pomodoro('Pomodoro', Duration(minutes: 25)),
high('high', Duration(minutes: 45), tipsId: ['45m/5m']),
Expand All @@ -87,3 +88,12 @@ enum EnergyType {
final String previewName;
final List<String>? tipsId;
}

extension EnergyTypeExtension on EnergyType {
static EnergyType fromString(String typeAsString) {
return EnergyType.values.firstWhere(
(element) => element.toString().split('.').last == typeAsString,
orElse: () => EnergyType.undefined,
);
}
}
29 changes: 29 additions & 0 deletions lib/domain/notifications_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'dart:io';

import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:infinite_horizons/presentation/core/theme_data.dart';

part 'package:infinite_horizons/infrastructure/notifications_repository.dart';

abstract class NotificationsController {
static NotificationsController? _instance;

static NotificationsController get instance =>
_instance ??= _NotificationsRepository();

void init();

Future send({
required DateTime date,
required String title,
required NotificationVariant variant,
String? body,
});

Future generalPermission();

Future preciseAlarmPermission();

Future cancelAllNotifications();
}
4 changes: 2 additions & 2 deletions lib/domain/player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ abstract class PlayerController {

static PlayerController get instance => _instance ??= _PlayerRepository();

void init();

void setIsSound(bool value);

bool isSound();

void initialize();

Future play(String fileName);
}
12 changes: 12 additions & 0 deletions lib/domain/preferences_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ abstract class PreferencesController {
_instance ??= _PreferencesRepository();

Future init();
Future reload();
guyluz11 marked this conversation as resolved.
Show resolved Hide resolved

String? getString(String key);

int? getInt(String key);

bool? getBool(String key);

Duration? getDuration(String key);

DateTime? getDateTime(String key);

void remove(String key);

void setString(String key, String value);

void setInt(String key, int value);

void setBool(String key, bool value);

void setDuration(String key, Duration value);

void setDateTime(String key, DateTime value);
}
14 changes: 7 additions & 7 deletions lib/domain/study_type_abstract.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:infinite_horizons/domain/timer_states.dart';
import 'package:infinite_horizons/domain/energy_level.dart';
import 'package:infinite_horizons/domain/tip.dart';

abstract class StudyTypeAbstract {
StudyTypeAbstract(this.studyType);
StudyTypeAbstract(this.tipType);

static StudyTypeAbstract? instance;

TipType studyType;
TipType tipType;

TimerStates? _timerStates;
EnergyLevel? _timerStates;

void setTimerStates(EnergyType energy) =>
_timerStates = TimerStates.fromEnergyType(energy);
_timerStates = EnergyLevel.fromEnergyType(energy);

TimerStates getTimerStates() =>
_timerStates ?? TimerStates.fromEnergyType(EnergyType.undefined);
EnergyLevel getTimerStates() =>
_timerStates ?? EnergyLevel.fromEnergyType(EnergyType.undefined);

@protected
List<Tip> tips = [];
Expand Down
124 changes: 124 additions & 0 deletions lib/infrastructure/notifications_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
part of 'package:infinite_horizons/domain/notifications_controller.dart';

class _NotificationsRepository extends NotificationsController {
late AwesomeNotifications controller;
int notificationIdCounter = 1;

@override
void init() {
controller = AwesomeNotifications();
controller.initialize(
'resource://drawable/res_app_icon',
[
NotificationChannel(
channelKey: NotificationVariant.studyEnded.channelKey,
channelName: 'Study Ended',
channelDescription: 'Study time ended',
soundSource: 'resource://raw/session_completed',
defaultColor: AppThemeData.logoBackgroundColor,
ledColor: AppThemeData.logoBackgroundColor,
criticalAlerts: true,
),
NotificationChannel(
channelKey: NotificationVariant.breakEnded.channelKey,
channelName: 'Break Ended',
channelDescription: 'Break time ended',
soundSource: 'resource://raw/break_ended',
defaultColor: AppThemeData.logoBackgroundColor,
ledColor: AppThemeData.logoBackgroundColor,
criticalAlerts: true,
),
],
);

controller.setListeners(
onActionReceivedMethod: onActionReceivedMethod,
onNotificationCreatedMethod: onNotificationCreatedMethod,
onNotificationDisplayedMethod: onNotificationDisplayedMethod,
onDismissActionReceivedMethod: onDismissActionReceivedMethod,
);
}

@override
Future send({
required DateTime date,
required String title,
required NotificationVariant variant,
String? body,
}) async =>
controller.createNotification(
schedule: NotificationCalendar.fromDate(
date: date,
allowWhileIdle: true,
preciseAlarm: true,
),
content: NotificationContent(
id: notificationIdCounter++,
channelKey: variant.channelKey,
title: title,
body: body,
criticalAlert: true,
wakeUpScreen: true,
),
);

@override
Future generalPermission() =>
controller.requestPermissionToSendNotifications();

@override
Future preciseAlarmPermission() async {
if (await isAndroid12OrAbove()) {
controller.showAlarmPage();
}
}

Future<bool> isAndroid12OrAbove() async {
if (Platform.isAndroid) {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
final sdkInt = androidInfo.version.sdkInt;
// Android 12 is API level 31
return sdkInt >= 31;
}
return false;
}

@override
Future cancelAllNotifications() => controller.cancelAll();

/// Use this method to detect when the user taps on a notification or action button
/// Also capture when there is a message from firebase messaging
@pragma("vm:entry-point")
static Future<void> onActionReceivedMethod(
ReceivedAction receivedAction,
) async {}

/// Use this method to detect when a new notification or a schedule is created
@pragma("vm:entry-point")
static Future<void> onNotificationCreatedMethod(
ReceivedNotification receivedNotification,
) async {}

/// Use this method to detect every time that a new notification is displayed
@pragma("vm:entry-point")
static Future<void> onNotificationDisplayedMethod(
ReceivedNotification receivedNotification,
) async {}

/// Use this method to detect if the user dismissed a notification
@pragma("vm:entry-point")
static Future<void> onDismissActionReceivedMethod(
ReceivedAction receivedAction,
) async {}
}

enum NotificationVariant {
studyEnded('study_ended'),
breakEnded('break_ended'),
;

const NotificationVariant(this.channelKey);

final String channelKey;
}
Loading
Loading