Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
9 changes: 7 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ android {
}

compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
Expand All @@ -30,7 +31,7 @@ android {

defaultConfig {
applicationId = "com.ryan.anymex"
minSdk = 23
minSdkVersion = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
Expand Down Expand Up @@ -58,11 +59,15 @@ android {

buildTypes {
release {
signingConfig = signingConfigs.release
signingConfig = signingConfigs.debug
}
}
}

flutter {
source = "../.."
}

dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
}
68 changes: 68 additions & 0 deletions lib/controllers/notification/notification_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:anymex/controllers/services/anilist/anilist_auth.dart';
import 'package:anymex/controllers/services/anilist/anilist_queries.dart';
import 'package:anymex/models/Anilist/anilist_notification.dart';
import 'package:anymex/utils/notification_service.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:anymex/utils/logger.dart';

class NotificationController extends GetxController {
final anilistAuth = Get.find<AnilistAuth>();
var notifications = <AnilistNotification>[].obs;
var isLoading = false.obs;

@override
void onInit() {
super.onInit();
fetchNotifications();

// Refetch when user list changes (login/sync)
ever(anilistAuth.animeList, (_) {
fetchNotifications();
});
}

Future<void> fetchNotifications() async {
if (!anilistAuth.isLoggedIn.value) {
notifications.clear();
return;
}

final token = anilistAuth.storage.get('auth_token');

isLoading.value = true;
try {
final response = await http.post(
Uri.parse('https://graphql.anilist.co'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: json.encode({
'query': notificationQuery,
'variables': {
'page': 1,
'perPage': 30,
}
}),
);

if (response.statusCode == 200) {
final data = json.decode(response.body);
final rawNotifications = data['data']['Page']['notifications'] as List;

notifications.value = rawNotifications
.map((e) => AnilistNotification.fromJson(e))
.toList();

} else {
Logger.e("Failed to fetch notifications: ${response.body}");
}
} catch (e) {
Logger.e("Error fetching notifications: $e");
} finally {
isLoading.value = false;
}
}
}
85 changes: 85 additions & 0 deletions lib/controllers/services/anilist/anilist_queries.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,88 @@ averageScore
}
}
''';

const airingScheduleQuery = '''
query (\$page: Int, \$perPage: Int, \$airingAtGreater: Int, \$airingAtLesser: Int) {
Page(page: \$page, perPage: \$perPage) {
pageInfo {
hasNextPage
total
}
airingSchedules(
airingAt_greater: \$airingAtGreater
airingAt_lesser: \$airingAtLesser
sort: TIME_DESC
) {
id
episode
airingAt
media {
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
medium
color
}
type
format
averageScore
favourites
isAdult
}
}
}
}
''';

const notificationQuery = '''
query (\$page: Int, \$perPage: Int) {
Page(page: \$page, perPage: \$perPage) {
pageInfo {
hasNextPage
total
}
notifications(type_in: [AIRING, RELATED_MEDIA_ADDITION], resetNotificationCount: false) {
... on AiringNotification {
id
type
episode
contexts
createdAt
media {
id
title {
userPreferred
}
coverImage {
large
}
type
}
}
... on RelatedMediaAdditionNotification {
id
type
context
createdAt
media {
id
title {
userPreferred
}
coverImage {
large
}
type
}
}
}
}
}
''';
23 changes: 23 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ import 'package:anymex/widgets/custom_widgets/anymex_titlebar.dart';
import 'package:anymex/widgets/helper/platform_builder.dart';
import 'package:anymex/widgets/non_widgets/settings_sheet.dart';
import 'package:anymex/widgets/non_widgets/snackbar.dart';
import 'package:anymex/utils/notification_service.dart';
import 'package:anymex/utils/background_service.dart';
import 'package:workmanager/workmanager.dart';
import 'package:anymex/controllers/notification/notification_controller.dart';
import 'package:app_links/app_links.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
Expand Down Expand Up @@ -109,6 +113,24 @@ void main(List<String> args) async {
_initializeGetxController();
initializeDateFormatting();
MediaKit.ensureInitialized();
await NotificationService.init();

// Initialize Workmanager for background tasks
if (Platform.isAndroid) {
await Workmanager().initialize(
callbackDispatcher,
isInDebugMode: false, // Set to true for debugging frequency
);
await Workmanager().registerPeriodicTask(
"fetchEpisodeTask",
fetchBackgroundEpisodeTask,
frequency: const Duration(minutes: 15),
constraints: Constraints(
networkType: NetworkType.connected,
),
);
}

if (!Platform.isAndroid && !Platform.isIOS) {
await windowManager.ensureInitialized();
if (Platform.isWindows) {
Expand Down Expand Up @@ -197,6 +219,7 @@ void _initializeGetxController() async {
Get.put(GreetingController());
Get.put(CommentumService());
Get.put(CommentPreloader());
Get.put(NotificationController());
Get.lazyPut(() => CacheController());
// DownloadManagerBinding.initializeDownloadManager();
}
Expand Down
59 changes: 59 additions & 0 deletions lib/models/Anilist/anilist_notification.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class AnilistNotification {
final int id;
final String type;
final int? episode;
final String? context;
final int createdAt;
final NotificationMedia? media;

AnilistNotification({
required this.id,
required this.type,
this.episode,
this.context,
required this.createdAt,
this.media,
});

factory AnilistNotification.fromJson(Map<String, dynamic> json) {
String? contextData;
if (json['contexts'] != null && (json['contexts'] as List).isNotEmpty) {
contextData = (json['contexts'] as List).first;
} else {
contextData = json['context'];
}

return AnilistNotification(
id: json['id'],
type: json['type'],
episode: json['episode'],
context: contextData,
createdAt: json['createdAt'],
media:
json['media'] != null ? NotificationMedia.fromJson(json['media']) : null,
);
}
}

class NotificationMedia {
final int id;
final String title;
final String coverImage;
final String type;

NotificationMedia({
required this.id,
required this.title,
required this.coverImage,
required this.type,
});

factory NotificationMedia.fromJson(Map<String, dynamic> json) {
return NotificationMedia(
id: json['id'],
title: json['title']['userPreferred'],
coverImage: json['coverImage']['large'],
type: json['type'],
);
}
}
2 changes: 2 additions & 0 deletions lib/models/Media/media.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Media {
DateTime? createdAt;
bool? isAdult;
String? sourceName;
String? extraData;

// String get uniqueId => "$id-${serviceType.name}";
String get uniqueId => id.split('*').first;
Expand Down Expand Up @@ -82,6 +83,7 @@ class Media {
this.mediaContent,
required this.serviceType,
this.sourceName,
this.extraData,
DateTime? createdAt})
: createdAt = DateTime.now();

Expand Down
Loading