Skip to content

Commit

Permalink
Merge pull request #45 from andannn/notification
Browse files Browse the repository at this point in the history
notification function
  • Loading branch information
andannn authored Nov 11, 2023
2 parents b3a8915 + 71e8349 commit 4dbf38c
Show file tree
Hide file tree
Showing 87 changed files with 6,513 additions and 331 deletions.
84 changes: 58 additions & 26 deletions lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:aniflow/core/data/media_list_repository.dart';
import 'package:aniflow/core/data/settings_repository.dart';
import 'package:aniflow/core/design_system/theme/colors.dart';
import 'package:aniflow/core/design_system/widget/vertical_animated_scale_switcher.dart';
import 'package:aniflow/feature/auth/bloc/auth_bloc.dart';
import 'package:aniflow/feature/discover/bloc/discover_bloc.dart';
import 'package:aniflow/feature/media_track/bloc/track_bloc.dart';
import 'package:dynamic_color/dynamic_color.dart';
Expand Down Expand Up @@ -108,12 +109,28 @@ class _AnimeTrackerAppScaffoldState extends State<AnimeTrackerAppScaffold> {
var currentNavigation = TopLevelNavigation.discover;
var needHideNavigationBar = false;
var showFloatingButton = true;
late SettingsRepository userDataRepository = SettingsRepositoryImpl();
final userDataRepository = SettingsRepositoryImpl();
final authRepository = AuthRepositoryImpl();
late StreamSubscription _mediaTypeSub;
late StreamSubscription _authSub;

MediaType _mediaType = MediaType.anime;
bool? _isLogIn;

bool get isAnime => _mediaType == MediaType.anime;

List<TopLevelNavigation> get _topLevelNavigationList => _isLogIn == true
? [
TopLevelNavigation.discover,
TopLevelNavigation.track,
TopLevelNavigation.social,
TopLevelNavigation.profile,
]
: [
TopLevelNavigation.discover,
TopLevelNavigation.track,
];

@override
void initState() {
super.initState();
Expand All @@ -126,12 +143,18 @@ class _AnimeTrackerAppScaffoldState extends State<AnimeTrackerAppScaffold> {
});
});
_mediaTypeSub = userDataRepository.getMediaTypeStream().distinct().listen(
(mediaType) {
(mediaType) {
setState(() {
_mediaType = mediaType;
});
},
);
_authSub =
authRepository.getAuthedUserStream().distinct().listen((userData) {
setState(() {
_isLogIn = userData != null;
});
});
}

@override
Expand All @@ -140,41 +163,51 @@ class _AnimeTrackerAppScaffoldState extends State<AnimeTrackerAppScaffold> {

animeTrackerRouterDelegate.dispose();
_mediaTypeSub.cancel();
_authSub.cancel();
}

@override
Widget build(BuildContext context) {
globalContext = context;

if (_isLogIn == null) {
/// user login state is unknown.
return const SizedBox();
}

return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
DiscoverBloc(
settingsRepository: context.read<SettingsRepository>(),
mediaRepository: context.read<MediaInformationRepository>(),
authRepository: context.read<AuthRepository>(),
animeTrackListRepository: context.read<MediaListRepository>(),
),
create: (context) => DiscoverBloc(
settingsRepository: context.read<SettingsRepository>(),
mediaRepository: context.read<MediaInformationRepository>(),
authRepository: context.read<AuthRepository>(),
animeTrackListRepository: context.read<MediaListRepository>(),
),
),
BlocProvider(
create: (context) => TrackBloc(
settingsRepository: context.read<SettingsRepository>(),
mediaListRepository: context.read<MediaListRepository>(),
authRepository: context.read<AuthRepository>(),
),
),
BlocProvider(
create: (context) =>
TrackBloc(
settingsRepository: context.read<SettingsRepository>(),
mediaListRepository: context.read<MediaListRepository>(),
authRepository: context.read<AuthRepository>(),
),
create: (context) => AuthBloc(
authRepository: context.read<AuthRepository>(),
),
),
],
child: Scaffold(
body: Router(
routerDelegate: animeTrackerRouterDelegate,
backButtonDispatcher: RootBackButtonDispatcher()),
floatingActionButton: showFloatingButton
? _buildTopFloatingActionButton()
: null,
floatingActionButton:
showFloatingButton ? _buildTopFloatingActionButton() : null,
bottomNavigationBar: VerticalScaleSwitcher(
visible: !needHideNavigationBar,
child: _animeTrackerNavigationBar(
navigationList: _topLevelNavigationList,
selected: currentNavigation,
onNavigateToDestination: (navigation) async {
animeTrackerRouterDelegate.navigateToTopLevelPage(navigation);
Expand All @@ -185,13 +218,14 @@ class _AnimeTrackerAppScaffoldState extends State<AnimeTrackerAppScaffold> {
);
}

Widget _animeTrackerNavigationBar({required TopLevelNavigation selected,
required Function(TopLevelNavigation) onNavigateToDestination}) {
Widget _animeTrackerNavigationBar(
{required List<TopLevelNavigation> navigationList,
required TopLevelNavigation selected,
required Function(TopLevelNavigation) onNavigateToDestination}) {
final currentIndex = TopLevelNavigation.values.indexOf(selected);
return NavigationBar(
destinations: TopLevelNavigation.values
.map((navigation) =>
navigation.toBottomNavigationBarItem(
destinations: navigationList
.map((navigation) => navigation.toBottomNavigationBarItem(
isSelected: navigation == selected))
.toList(),
onDestinationSelected: (index) {
Expand All @@ -214,9 +248,7 @@ class _AnimeTrackerAppScaffoldState extends State<AnimeTrackerAppScaffold> {
}
},
isExtended: true,
icon: isAnime
? const Icon(Icons.palette_rounded)
: const Icon(Icons.map),
icon: isAnime ? const Icon(Icons.palette_rounded) : const Icon(Icons.map),
label: Text(isAnime ? 'Anime' : 'Manga'),
);
}
Expand Down
107 changes: 102 additions & 5 deletions lib/app/local/util/string_resource_util.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import 'dart:convert';

import 'package:aniflow/app/local/ani_flow_localizations.dart';
import 'package:aniflow/core/common/model/activity_filter_type.dart';
import 'package:aniflow/core/common/model/anime_season.dart';
import 'package:aniflow/core/common/model/anime_source.dart';
import 'package:aniflow/core/common/model/character_role.dart';
import 'package:aniflow/core/common/model/media_status.dart';
import 'package:aniflow/core/common/util/time_util.dart';
import 'package:aniflow/core/data/model/activity_model.dart';
import 'package:aniflow/core/data/model/media_model.dart';
import 'package:aniflow/core/data/model/media_title_modle.dart';
import 'package:aniflow/core/data/model/notification_model.dart';
import 'package:aniflow/core/data/notification_repository.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

extension AnimeSourceEx on AnimeSource {
Expand Down Expand Up @@ -51,6 +58,10 @@ extension AnimeStatusEx on MediaStatus {
return AFLocalizations.of(context).animeFinished;
case MediaStatus.notYetReleased:
return AFLocalizations.of(context).animeNotYetReleased;
case MediaStatus.cancelled:
return 'Canceled';
case MediaStatus.hiatus:
return 'Hiatus';
}
}
}
Expand Down Expand Up @@ -105,9 +116,95 @@ extension AnimeModelEx on MediaModel {
}

extension ActivityFilterTypeEx on ActivityFilterType {
String label(BuildContext context) => switch(this) {
ActivityFilterType.all => 'All',
ActivityFilterType.text => 'Text',
ActivityFilterType.list => 'List',
};
String label(BuildContext context) => switch (this) {
ActivityFilterType.all => 'All',
ActivityFilterType.text => 'Text',
ActivityFilterType.list => 'List',
};
}

extension NotificationCategoryEx on NotificationCategory {
String label(BuildContext context) => switch (this) {
NotificationCategory.all => 'All',
NotificationCategory.airing => 'Airing',
NotificationCategory.activity => 'Activity',
NotificationCategory.follows => 'Follows',
NotificationCategory.media => 'Media',
};
}

extension AiringNotificationEx on AiringNotification {
List<TextSpan> createTextSpanList(BuildContext buildContext,
{required VoidCallback onMediaTextClick}) {
final List contextList = jsonDecode(context);
final colorScheme = Theme.of(buildContext).colorScheme;
return [
TextSpan(text: '${contextList[0]} $episode ${contextList[1]}'),
TextSpan(
text: media.title?.native,
style: TextStyle(color: colorScheme.tertiary),
recognizer: TapGestureRecognizer()..onTap = onMediaTextClick,
),
TextSpan(text: contextList[2]),
];
}
}

extension FollowNotificationEx on FollowNotification {
List<TextSpan> createTextSpanList(BuildContext buildContext,
{required VoidCallback onUserTextClick}) {
final colorScheme = Theme.of(buildContext).colorScheme;
return [
TextSpan(
text: user.name,
style: TextStyle(color: colorScheme.tertiary),
recognizer: TapGestureRecognizer()..onTap = onUserTextClick,
),
TextSpan(text: context),
];
}
}

extension ActivityNotificationEx on ActivityNotification {
List<TextSpan> createTextSpanList(BuildContext buildContext,
{required VoidCallback onUserTextClick}) {
final colorScheme = Theme.of(buildContext).colorScheme;
return [
TextSpan(
text: user.name,
style: TextStyle(color: colorScheme.tertiary),
recognizer: TapGestureRecognizer()..onTap = onUserTextClick,
),
TextSpan(text: context),
];
}
}

extension MediaNotificationEx on MediaNotification {
List<TextSpan> createTextSpanList(BuildContext buildContext,
{required VoidCallback onUserTextClick}) {
final colorScheme = Theme.of(buildContext).colorScheme;
return [
TextSpan(
text: media.title?.native,
style: TextStyle(color: colorScheme.tertiary),
recognizer: TapGestureRecognizer()..onTap = onUserTextClick,
),
TextSpan(text: context),
];
}
}
extension ListActivityModelEx on ListActivityModel {
List<TextSpan> createTextSpanList(BuildContext buildContext,
{required VoidCallback onMediaClick}) {
final colorScheme = Theme.of(buildContext).colorScheme;
return [
TextSpan(text: '${status.toString()} $progress of '),
TextSpan(
text: media.title!.getLocalTitle(buildContext),
style: TextStyle(color: colorScheme.tertiary),
recognizer: TapGestureRecognizer()..onTap = onMediaClick,
),
];
}
}
7 changes: 7 additions & 0 deletions lib/app/navigation/ani_flow_route_path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:aniflow/feature/detail_media/detail_media.dart';
import 'package:aniflow/feature/discover/discover.dart';
import 'package:aniflow/feature/media_page/media_page.dart';
import 'package:aniflow/feature/media_track/media_track.dart';
import 'package:aniflow/feature/notification/notification.dart';
import 'package:aniflow/feature/profile/profile.dart';
import 'package:aniflow/feature/social/social.dart';
import 'package:aniflow/feature/staff_page/staff_page.dart';
Expand Down Expand Up @@ -97,6 +98,10 @@ class AiringScheduleRoutePath extends AniFlowRoutePath {
const AiringScheduleRoutePath() : super(isFullScreen: true);
}

class NotificationRoutePath extends AniFlowRoutePath {
const NotificationRoutePath() : super(isFullScreen: true);
}

extension AniFlowRoutePathEx on AniFlowRoutePath {
Page generatePage() {
switch (this) {
Expand Down Expand Up @@ -134,6 +139,8 @@ extension AniFlowRoutePathEx on AniFlowRoutePath {
return const AiringSchedule(key: ValueKey('AiringSchedule'));
case SearchRoutePath():
return const SearchPage(key: ValueKey('SearchPage'));
case NotificationRoutePath():
return const NotificationPage(key: ValueKey('NotificationPage'));
default:
return const MaterialPage(child: SizedBox());
}
Expand Down
4 changes: 4 additions & 0 deletions lib/app/navigation/ani_flow_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ class AFRouterDelegate extends RouterDelegate<AniFlowRoutePath>
_pushAsSingleton(const SearchRoutePath());
}

void navigateToNotification() {
_pushAsSingleton(const NotificationRoutePath());
}

void navigateToUserProfile(String userId) {
_pushAsSingleton(UserProfileRoutePath(userId));
}
Expand Down
22 changes: 17 additions & 5 deletions lib/core/common/model/media_status.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@

import 'package:freezed_annotation/freezed_annotation.dart';

@JsonEnum()
enum MediaStatus {
@JsonValue('RELEASING')
releasing('RELEASING'),
/// Has completed and is no longer being released
@JsonValue('FINISHED')
finished('FINISHED'),

/// Currently releasing
@JsonValue('RELEASING')
releasing('RELEASING'),

/// To be released at a later date
@JsonValue('NOT_YET_RELEASED')
notYetReleased('NOT_YET_RELEASED');
notYetReleased('NOT_YET_RELEASED'),

/// Ended before the work could be finished
@JsonValue('CANCELLED')
cancelled('CANCELLED'),

// ignore: lines_longer_than_80_chars
/// Version 2 only. Is currently paused from releasing and will resume at a later date.
@JsonValue('HIATUS')
hiatus('HIATUS');

final String sqlTypeString;

const MediaStatus(this.sqlTypeString);
}

10 changes: 8 additions & 2 deletions lib/core/common/util/global_static_constants.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

// ignore_for_file: lines_longer_than_80_chars

mixin Config {
Expand All @@ -10,8 +9,15 @@ mixin Config {

static const int profilePageDefaultPerPageCount = 6;
static const int activityPageDefaultPerPageCount = 50;
static const int notificationPageDefaultPerPageCount = 25;

/// Detail page consts.
static const double detailPagePreviewItemHeight = 133.0;
static const int characterColumnCount = 3;
static const int staffColumnCount = 2;
}

/// Only for test
var isUnitTest = false;
String testToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImNlNjM4MGQ2MTY0YjFmYjdhZmUzZjA0NTM0ZTVlNDJlZmM2MDQwNjNhYjAyYjdjOWRmYWUwODVmNTQ4Yjk0YWEzNDZmZDE5MTEyNDU3Njc4In0.eyJhdWQiOiIxNDQwOSIsImp0aSI6ImNlNjM4MGQ2MTY0YjFmYjdhZmUzZjA0NTM0ZTVlNDJlZmM2MDQwNjNhYjAyYjdjOWRmYWUwODVmNTQ4Yjk0YWEzNDZmZDE5MTEyNDU3Njc4IiwiaWF0IjoxNjk4ODI5MDgyLCJuYmYiOjE2OTg4MjkwODIsImV4cCI6MTczMDQ1MTQ4Miwic3ViIjoiNjM3ODM5MyIsInNjb3BlcyI6W119.sVv96g4xST4QnJucV5O3vNOy2hseu8EZGTsOpx8SOC0Zgp1LTAQj585U32QY900IF8VXlikoFvTPWM0cU0QASx7mCnTeG75vMNxUpzTOoZ6cQbVUFKF0NzOVPkWldVt4m7Wn79wrwGa91Oal6Hj5r8TiICzg_vOZb-dTpkWFiAeZ2CEcuw7099_FaBKLNMspMGPwhRh7xdfiLTvMTRLUdPtwz6TQf7lLtsKVx1Qz0LLIuZluEVb5gBu43N5GHPRbj2JwDJQxsZ6nL95QwrA2LOBIasYltr90mShC78gj0eDOM7Hf84Gioekz26-VJTivWxXSS3mklGPq4WAtHVzfEBdkduhRl-GySsGQoQZamDZdyc4ikii_AOlKH6-aPRPVj5KI1ecC4Wiw5pZHGntGoP2m8fJlprGufawpwbjqo-_Ki0qPyJSSYqJgDWekFC2Wm8-IkyZzDsCca8NXo_EkAow-j54rqLldEZrCT1pzxI9YIHUmsheVMkQfA5-5VCS-zL_qW5o0DYM1P8QVyKJGtgCrL83UOABANLSC__SKhXIH4eiD6LI-lCeiVgaiIv6lwzn2PATFvOJeTv0uUJGkhSLELIJlDhDZ88TwwHUzbxNVjXj5OnywAGHYO09SdZNs1YI3ATccTzMq96SWXN9rzY1If7h8XKts-NzX1esINjM';
const String testToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijc4ZWQ1YTk5ZDFkNTU2NDBkMTRkN2E5N2I1YWMyNDc3MjIyODFhYjM2NzE4MmFiN2VjNDg2ZTJjOWQ0MTA2MDZkN2ZlOGZkYzA1YjJlOGQ2In0.eyJhdWQiOiIxNDQwOSIsImp0aSI6Ijc4ZWQ1YTk5ZDFkNTU2NDBkMTRkN2E5N2I1YWMyNDc3MjIyODFhYjM2NzE4MmFiN2VjNDg2ZTJjOWQ0MTA2MDZkN2ZlOGZkYzA1YjJlOGQ2IiwiaWF0IjoxNjk4NTY2MzU0LCJuYmYiOjE2OTg1NjYzNTQsImV4cCI6MTczMDE4ODc1NCwic3ViIjoiNjM3ODM5MyIsInNjb3BlcyI6W119.cjnTU0bSGzQnjxbAy-wkzRqiI5r2zAI_XmOX8teX0MZuceDj7qiBQQ6An-p6Ue30LYSI7V5Tw4ocChZn1bRsg6sRFCPAmE8eBbV4XBGjOhlAb9ciqMgTmVeND6bz0uWO1lr3D0fLfmQKPchrZAwb1-aVDhMkI3Ba4bC1VzxfbGPpw3IzbXjbAg1kFStENCvn3fTXTX53ndNGuPlbS6l8tfOlgYSK7dm0le4JrJAEIlm_Om4WYb6ij42sI0WK1pJY5Lzpar-K0KxaubN9poNKXa81PX65DABzywHXrEi8_D7_O_DkIVsKPjU5dAhXUEhmukFkkLDbRgn7np0i8A-wAfTrVq3tLPvUkDoqdyH105mmwXR_nwO3ZqKJtCnolUok289my02qP1yA0uSmq0nJ51ikI3CEnk_H-jCz5OcVAUhN2qNRb3fhx0fNekBBfaUuJqbHr7usgaNAWRu_vQX0LGs7daClKl1k5FBtdrsJGwSkbY13ghKH42pmBVlY-I9oS8ocznuMsVtyo7QfY6oaMv9kZBuelMto7pM28z61S__qo2jsKzr_2I5uAH3avjUY28G8M3r0r3nkkXqMu_BPn3T95LOLTCghtwx2cE4LARBpZ_1t9rYFUzDb05HeARrqWzbhOwbm9c0N5RM8guq6O1q8kAVZFNLTLjK25ycBuiw';
2 changes: 1 addition & 1 deletion lib/core/data/auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class AuthRepositoryImpl implements AuthRepository {
return null;
} else {
final userEntity = await userDataDao.getUserData(userId);
return UserModel.fromDatabaseModel(userEntity);
return UserModel.fromEntity(userEntity);
}
});
}
Expand Down
Loading

0 comments on commit 4dbf38c

Please sign in to comment.