New Feature:

- Push notification!
This commit is contained in:
Linloir 2022-10-23 22:44:02 +08:00
parent 5834bcaa58
commit acc5f493af
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
14 changed files with 225 additions and 23 deletions

View File

@ -30,6 +30,7 @@ android {
ndkVersion flutter.ndkVersion
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@ -51,6 +52,7 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
buildTypes {
@ -68,4 +70,10 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

27
android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,27 @@
## Gson rules
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

View File

@ -11,7 +11,9 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
@ -30,6 +32,11 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<service
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
android:exported="false"
android:stopWithTask="false"/>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/*" />

View File

@ -7,7 +7,17 @@ import Flutter
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// This is required to make any communication available in the action isolate.
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
GeneratedPluginRegistrant.register(with: registry)
}
GeneratedPluginRegistrant.register(with: self)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:28
* @LastEditTime : 2022-10-22 21:08:39
* @LastEditTime : 2022-10-23 22:37:55
* @Description :
*/
@ -9,18 +9,22 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/cubit/home_state.dart';
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
class HomeCubit extends Cubit<HomeState> {
HomeCubit({
required this.localServiceRepository,
required this.tcpRepository,
required this.pageController
required this.pageController,
required this.localNotificationsPlugin,
required this.userRepository
}): super(const HomeState(page: HomePagePosition.message, status: HomePageStatus.initializing)) {
pageController.addListener(() {
emit(state.copyWith(page: HomePagePosition.fromValue((pageController.page ?? 0).round())));
@ -59,7 +63,9 @@ class HomeCubit extends Cubit<HomeState> {
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final PageController pageController;
final UserRepository userRepository;
late final StreamSubscription subscription;
final FlutterLocalNotificationsPlugin localNotificationsPlugin;
void switchPage(HomePagePosition newPage) {
pageController.animateToPage(
@ -77,6 +83,36 @@ class HomeCubit extends Cubit<HomeState> {
case TCPResponseType.forwardMessage: {
response as ForwardMessageResponse;
await localServiceRepository.storeMessages([response.message]);
var curUser = (await SharedPreferences.getInstance()).getInt('userid');
if(response.message.senderID != curUser) {
//Push notification via flutter local notification
const androidNotificationDetails = AndroidNotificationDetails(
'0',
'New Messages',
channelDescription: 'New messages',
importance: Importance.max,
priority: Priority.max,
enableVibration: true,
enableLights: true,
visibility: NotificationVisibility.private
);
const iosNotificationDetails = DarwinNotificationDetails();
const linuxNotificationDetails = LinuxNotificationDetails();
const notificationDetails = NotificationDetails(
android: androidNotificationDetails,
iOS: iosNotificationDetails,
macOS: iosNotificationDetails,
linux: linuxNotificationDetails
);
var userName = userRepository.getUserInfo(userid: response.message.senderID).userName;
await localNotificationsPlugin.show(
response.message.contentmd5.hashCode,
'New Message',
'$userName: ${response.message.contentDecoded}',
notificationDetails,
payload: response.message.contentmd5
);
}
break;
}
case TCPResponseType.fetchMessage: {

View File

@ -1,12 +1,13 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:05:08
* @LastEditTime : 2022-10-23 12:14:36
* @LastEditTime : 2022-10-23 22:37:02
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/cubit/home_cubit.dart';
import 'package:tcp_client/home/cubit/home_state.dart';
@ -27,6 +28,7 @@ class HomePage extends StatelessWidget with WindowListener {
required this.userID,
required this.localServiceRepository,
required this.tcpRepository,
required this.localNotificationsPlugin,
super.key
});
//TODO: listen to file storage
@ -34,15 +36,18 @@ class HomePage extends StatelessWidget with WindowListener {
final int userID;
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final FlutterLocalNotificationsPlugin localNotificationsPlugin;
static Route<void> route({
required int userID,
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository
required TCPRepository tcpRepository,
required FlutterLocalNotificationsPlugin localNotificationsPlugin,
}) => MaterialPageRoute<void>(builder: (context) => HomePage(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
localNotificationsPlugin: localNotificationsPlugin,
));
@override
@ -76,7 +81,9 @@ class HomePage extends StatelessWidget with WindowListener {
create: (context) => HomeCubit(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
pageController: PageController()
pageController: PageController(),
localNotificationsPlugin: localNotificationsPlugin,
userRepository: context.read<UserRepository>()
),
)
],

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:36:12
* @LastEditTime : 2022-10-20 11:45:15
* @LastEditTime : 2022-10-23 22:10:44
* @Description :
*/
@ -57,7 +57,8 @@ class MyProfilePage extends StatelessWidget {
Future.delayed(const Duration(seconds: 1)).then((_) {
Navigator.of(context).pushAndRemoveUntil(LoginPage.route(
localServiceRepository: context.read<LocalServiceRepository>(),
tcpRepository: context.read<TCPRepository>()
tcpRepository: context.read<TCPRepository>(),
localNotificationsPlugin: context.read<HomeCubit>().localNotificationsPlugin
), (route) => false);
});
}

View File

@ -1,12 +1,13 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 15:06:30
* @LastEditTime : 2022-10-23 10:15:11
* @LastEditTime : 2022-10-23 22:12:07
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:formz/formz.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/home_page.dart';
@ -21,19 +22,23 @@ class LoginPage extends StatelessWidget {
const LoginPage({
required this.localServiceRepository,
required this.tcpRepository,
required this.localNotificationsPlugin,
super.key
});
static Route<void> route({
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository
required TCPRepository tcpRepository,
required FlutterLocalNotificationsPlugin localNotificationsPlugin,
}) => MaterialPageRoute<void>(builder: (context) => LoginPage(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
tcpRepository: tcpRepository,
localNotificationsPlugin: localNotificationsPlugin,
));
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final FlutterLocalNotificationsPlugin localNotificationsPlugin;
@override
Widget build(BuildContext context) {
@ -64,7 +69,8 @@ class LoginPage extends StatelessWidget {
Navigator.of(context).pushReplacement(HomePage.route(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
tcpRepository: tcpRepository,
localNotificationsPlugin: localNotificationsPlugin
));
});
}
@ -96,7 +102,11 @@ class LoginPage extends StatelessWidget {
const Text('Does not have an account?'),
const SizedBox(width: 8,),
TextButton(
onPressed: () => Navigator.of(context).push(RegisterPage.route(localServiceRepository: localServiceRepository, tcpRepository: tcpRepository)),
onPressed: () => Navigator.of(context).push(RegisterPage.route(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
localNotificationsPlugin: localNotificationsPlugin
)),
style: ButtonStyle(
overlayColor: MaterialStateProperty.all(Colors.transparent),
foregroundColor: MaterialStateProperty.all(Colors.blue[800])

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-10 08:04:53
* @LastEditTime : 2022-10-23 12:18:17
* @LastEditTime : 2022-10-23 22:26:44
* @Description :
*/
import 'package:flutter/gestures.dart';
@ -13,8 +13,33 @@ import 'package:tcp_client/initialization/cubit/initialization_cubit.dart';
import 'package:tcp_client/initialization/cubit/initialization_state.dart';
import 'package:tcp_client/initialization/initialization_page.dart';
import 'package:tcp_client/login/login_page.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
void main() async {
// needed if you intend to initialize in the `main` function
WidgetsFlutterBinding.ensureInitialized();
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsDarwin =
DarwinInitializationSettings();
const LinuxInitializationSettings initializationSettingsLinux =
LinuxInitializationSettings(
defaultActionName: 'Open notification'
);
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin,
macOS: initializationSettingsDarwin,
linux: initializationSettingsLinux
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings
);
runApp(const MyApp());
}
@ -29,7 +54,7 @@ class MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
title: 'LChatClient',
theme: ThemeData(
primarySwatch: Colors.blue,
),
@ -49,7 +74,7 @@ class SplashPage extends StatelessWidget {
return BlocProvider<InitializationCubit>(
create: (context) {
return InitializationCubit(
serverAddress: '127.0.0.1',
serverAddress: 'chat.linloir.cn',
serverPort: 20706
);
},
@ -65,13 +90,15 @@ class SplashPage extends StatelessWidget {
Navigator.of(context).pushReplacement(HomePage.route(
userID: userID,
localServiceRepository: state.localServiceRepository!,
tcpRepository: state.tcpRepository!
tcpRepository: state.tcpRepository!,
localNotificationsPlugin: flutterLocalNotificationsPlugin
));
}
else {
Navigator.of(context).pushReplacement(LoginPage.route(
localServiceRepository: state.localServiceRepository!,
tcpRepository: state.tcpRepository!
tcpRepository: state.tcpRepository!,
localNotificationsPlugin: flutterLocalNotificationsPlugin
));
}
});

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 17:36:38
* @LastEditTime : 2022-10-23 10:15:31
* @LastEditTime : 2022-10-23 22:12:12
* @Description :
*/
/*
@ -13,6 +13,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:formz/formz.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/home_page.dart';
@ -26,19 +27,24 @@ class RegisterPage extends StatelessWidget {
const RegisterPage({
required this.localServiceRepository,
required this.tcpRepository,
required this.localNotificationsPlugin,
super.key
});
static Route<void> route({
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository
required TCPRepository tcpRepository,
required FlutterLocalNotificationsPlugin localNotificationsPlugin,
}) => MaterialPageRoute<void>(builder: (context) => RegisterPage(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
tcpRepository: tcpRepository,
localNotificationsPlugin: localNotificationsPlugin,
));
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final FlutterLocalNotificationsPlugin localNotificationsPlugin;
@override
Widget build(BuildContext context) {
@ -74,7 +80,8 @@ class RegisterPage extends StatelessWidget {
Navigator.of(context).pushAndRemoveUntil(HomePage.route(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
tcpRepository: tcpRepository,
localNotificationsPlugin: localNotificationsPlugin
), (route) => false);
});
}

View File

@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import flutter_local_notifications
import path_provider_macos
import screen_retriever
import shared_preferences_macos
@ -12,6 +13,7 @@ import sqflite
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View File

@ -1,6 +1,13 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.1"
async:
dependency: "direct main"
description:
@ -71,6 +78,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
dbus:
dependency: transitive
description:
name: dbus
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.8"
easy_debounce:
dependency: "direct main"
description:
@ -132,6 +146,27 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
url: "https://pub.flutter-io.cn"
source: hosted
version: "12.0.2"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -284,6 +319,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.0"
photo_view:
dependency: "direct main"
description:
@ -485,6 +527,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.12"
timezone:
dependency: transitive
description:
name: timezone
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.0"
typed_data:
dependency: transitive
description:
@ -520,6 +569,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0+2"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.0"
sdks:
dart: ">=2.18.2 <3.0.0"
flutter: ">=3.3.0-0"

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.2.0
version: 3.0.0
environment:
sdk: '>=2.18.2 <3.0.0'
@ -60,6 +60,7 @@ dependencies:
window_manager: ^0.2.7
# easy_image_viewer: ^1.1.0
photo_view: ^0.14.0
flutter_local_notifications: ^12.0.2
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.