Skip to content

Commit

Permalink
added support for background service
Browse files Browse the repository at this point in the history
  • Loading branch information
Rakibul73 committed Apr 25, 2023
1 parent b7be337 commit 83c9593
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 256 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The CR Alarm App is a Flutter-based Android app that auto set alarms based on th
- [x] Auto sets alarm
- [x] Making a api backend & hosting
- [x] Auto fetching tweets/post from api
- [ ] Adding Background service (App can be closed from recent tab)
- [x] Adding Background service (App can be closed from recent tab)
- [ ] Making a functional UI
- [ ] Allow users the ability to select whose tweets or posts they want to follow.
<!-- - [ ] Multi-language Support
Expand All @@ -42,6 +42,9 @@ flutter run

## Acknowledgements

- [Flutter Alarm Clock](https://pub.dev/packages/flutter_alarm_clock) library
- [Android Intent Plus](https://pub.dev/packages/android_intent_plus) library for alarm
- removed ~~[Flutter Alarm Clock](https://pub.dev/packages/flutter_alarm_clock) library for making an alarm~~
- [Twitter Tweets API](https://github.com/Rakibul73/twitter_tweets_api)
- [WorkManager](https://pub.dev/packages/workmanager) library for making periodic task
- [SharedPreferences](https://pub.dev/packages/shared_preferences) library for persistent storage
- [Twitter Developer API](https://developer.twitter.com/en/docs)
Binary file removed android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary file not shown.
Binary file removed android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary file not shown.
Binary file removed android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
202 changes: 83 additions & 119 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@ import 'dart:io';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'notification_service.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'package:flutter_alarm_clock/flutter_alarm_clock.dart';




import 'package:android_intent_plus/android_intent.dart';
import 'package:android_intent_plus/flag.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:workmanager/workmanager.dart';
import 'package:platform/platform.dart';


const simplePeriodicTask = "simplePeriodicTask";
const latestTimestampKey = "latestTimestamp";

@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
await _createAlarm();
// Return true when the task is completed successfully
return Future.value(true);
});
}

class PostHttpOverrides extends HttpOverrides{
@override
Expand All @@ -19,137 +31,89 @@ class PostHttpOverrides extends HttpOverrides{
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
tz.initializeTimeZones();
HttpOverrides.global = new PostHttpOverrides();
runApp(const MyApp());
}


class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);


// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CR Post Alarm App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

Future<Map<String, dynamic>> fetchDataFromApi(String apiUrl) async {
// Request internet permission
final response = await http.get(Uri.parse(apiUrl));

Future<void> _createAlarm() async {
// Retrieve the latest timestamp from shared preferences
final prefs = await SharedPreferences.getInstance();
String? latestTimestamp = prefs.getString(latestTimestampKey);
// Fetching data from API
final response = await http.get(Uri.parse("https://web-production-5866.up.railway.app/latest_tweet"));
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
final Map<String, dynamic> data = json.decode(response.body);
final String timestamp = data['timestamp'];
const int timeZoneDifferenceInHours = 6;
// Checking if the timestamp has changed or not
if (timestamp != latestTimestamp) {
// saving the latest timestamp to shared preferences for next time
latestTimestamp = timestamp;
prefs.setString(latestTimestampKey, latestTimestamp);
// setting the alarm 1 minute after the fetching data
// means when the app fetches the data, it will create the alarm 1 minute after that
final DateTime scheduleddatetimeNow = DateTime.now();
final DateTime scheduleddatetimeNowNow = scheduleddatetimeNow.add(const Duration(minutes: 1));
int hour = scheduleddatetimeNowNow.hour;
int minute = scheduleddatetimeNowNow.minute;
int hh = hour;
int mm = minute;

try {
final intent = AndroidIntent(
action: 'android.intent.action.SET_ALARM',
arguments: <String, dynamic>{
'android.intent.extra.alarm.HOUR': hh,
'android.intent.extra.alarm.MINUTES': mm,
'android.intent.extra.alarm.SKIP_UI': true,
'android.intent.extra.alarm.MESSAGE': 'CR Posted',
},
);
intent.launch();
}
catch (e) {
print('Error creating alarm: callbackDispatcher= $e');
}
}
else {
print("\ntimestamp === latestTimestamp\n");
}
}
else {
throw Exception('Failed to load data from API');
}
}

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
HttpOverrides.global = new PostHttpOverrides();
Workmanager().initialize(callbackDispatcher);
// Registering periodic task for every 15 minutes
Workmanager().registerPeriodicTask(simplePeriodicTask , simplePeriodicTask,
frequency: const Duration(minutes: 15));

runApp(MyApp());
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);

class MyApp extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
_MyAppState createState() => _MyAppState();
}

class _MyHomePageState extends State<MyHomePage> {
TimeOfDay _selectedTime = TimeOfDay.now();
// Add this variable to store the latest timestamp
String? _latestTimestamp;
// Add this variable to store the Timer instance
Timer? _timer;
final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
NotificationService.init();
_fetchApiAndSetAlarm(); // Call this function when the app starts
// Schedule a periodic Timer to call _fetchApiAndSetAlarm every 1 minutes
_timer = Timer.periodic(const Duration(minutes: 1), (Timer timer) {
_fetchApiAndSetAlarm();
});
}

@override
void dispose() {
// Cancel the Timer when the widget is disposed
_timer?.cancel();
super.dispose();
}




Future<void> _fetchApiAndSetAlarm() async {
try {
final Map<String, dynamic> data = await fetchDataFromApi('https://web-production-5866.up.railway.app/latest_tweet');
final String timestamp = data['timestamp'];
final String text = data['text'];
// Replace this value with the actual time difference in hours
const int timeZoneDifferenceInHours = 6; // Change this value according to your time difference


if (timestamp != _latestTimestamp) {
// If the timestamp has changed, set the alarm
_latestTimestamp = timestamp;

final DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss");
final DateTime dateInApiTimezone = dateFormat.parse(timestamp);
final DateTime dateInLocalTime = dateInApiTimezone.add(Duration(hours: timeZoneDifferenceInHours));
final DateTime scheduledDateTime = dateInLocalTime.add(const Duration(minutes: 2));


int hour = scheduledDateTime.hour;
int minute = scheduledDateTime.minute;


// NotificationService.scheduleAlarm(scheduledDateTime);
// Create an alarm at 23:59
FlutterAlarmClock.createAlarm(hour, minute , title: "CR Posted" );
}
} catch (error) {
_scaffoldMessengerKey.currentState?.showSnackBar(SnackBar(
content: Text('Failed to set alarm: $error'),
));
}
}


@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldMessengerKey,
appBar: AppBar(
title: const Text('CR Post Alarm App'),
centerTitle: true,
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('CR Post Alarm App'),
),
body: const Center(
child: Text("Relax & Sleep - Don't have to miss any post from CR"),
),
),
body: Center(
child: Column(children: <Widget>[
SizedBox(height: 30),
ElevatedButton(
onPressed: () {
// show alarm
FlutterAlarmClock.showAlarms();
},
child: const Text(
'Show Alarms',
style: TextStyle(fontSize: 20.0),
),
),
])),
);
}
}
}
40 changes: 0 additions & 40 deletions lib/notification_service.dart

This file was deleted.

8 changes: 2 additions & 6 deletions macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@
import FlutterMacOS
import Foundation

import audioplayers
import flutter_local_notifications
import path_provider_foundation
import shared_preferences_foundation

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersPlugin.register(with: registry.registrar(forPlugin: "AudioplayersPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
Loading

0 comments on commit 83c9593

Please sign in to comment.