diff --git a/android/app/build.gradle b/android/app/build.gradle index 4ac8c8b..0adb1be 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,22 +7,25 @@ plugins { android { namespace = "com.example.connect" - compileSdk = flutter.compileSdkVersion + // compileSdk = flutter.compileSdkVersion + compileSdk 34 //ndkVersion = flutter.ndkVersion ndkVersion = "25.1.8937393" compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = 17 + jvmTarget = "1.8" } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "eu.twonly" + multiDexEnabled true // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion @@ -43,3 +46,7 @@ android { flutter { source = "../.." } + +dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/logo.png b/android/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000..8b5e608 Binary files /dev/null and b/android/app/src/main/res/drawable/logo.png differ diff --git a/lib/main.dart b/lib/main.dart index 5a2d42f..2a1143f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,8 @@ +import 'dart:async'; + import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:provider/provider.dart'; import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api_provider.dart'; @@ -15,6 +19,135 @@ import 'src/app.dart'; late DbProvider dbProvider; late ApiProvider apiProvider; +/// Streams are created so that app can respond to notification-related events +/// since the plugin is initialized in the `main` function +final StreamController selectNotificationStream = + StreamController.broadcast(); + +const MethodChannel platform = + MethodChannel('dexterx.dev/flutter_local_notifications_example'); + +const String portName = 'notification_send_port'; + +class ReceivedNotification { + ReceivedNotification({ + required this.id, + required this.title, + required this.body, + required this.payload, + this.data, + }); + + final int id; + final String? title; + final String? body; + final String? payload; + final Map? data; +} + +String? selectedNotificationPayload; + +/// A notification action which triggers a url launch event +const String urlLaunchActionId = 'id_1'; + +/// A notification action which triggers a App navigation event +const String navigationActionId = 'id_3'; + +/// Defines a iOS/MacOS notification category for text input actions. +const String darwinNotificationCategoryText = 'textCategory'; + +/// Defines a iOS/MacOS notification category for plain actions. +const String darwinNotificationCategoryPlain = 'plainCategory'; + +@pragma('vm:entry-point') +void notificationTapBackground(NotificationResponse notificationResponse) { + // ignore: avoid_print + print('notification(${notificationResponse.id}) action tapped: ' + '${notificationResponse.actionId} with' + ' payload: ${notificationResponse.payload}'); + if (notificationResponse.input?.isNotEmpty ?? false) { + // ignore: avoid_print + print( + 'notification action tapped with input: ${notificationResponse.input}'); + } +} + +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + +int id = 0; + +Future setupPushNotification() async { + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings("logo"); + + final List darwinNotificationCategories = + [ + DarwinNotificationCategory( + darwinNotificationCategoryText, + actions: [ + DarwinNotificationAction.text( + 'text_1', + 'Action 1', + buttonTitle: 'Send', + placeholder: 'Placeholder', + ), + ], + ), + DarwinNotificationCategory( + darwinNotificationCategoryPlain, + actions: [ + DarwinNotificationAction.plain('id_1', 'Action 1'), + DarwinNotificationAction.plain( + 'id_2', + 'Action 2 (destructive)', + options: { + DarwinNotificationActionOption.destructive, + }, + ), + DarwinNotificationAction.plain( + navigationActionId, + 'Action 3 (foreground)', + options: { + DarwinNotificationActionOption.foreground, + }, + ), + DarwinNotificationAction.plain( + 'id_4', + 'Action 4 (auth required)', + options: { + DarwinNotificationActionOption.authenticationRequired, + }, + ), + ], + options: { + DarwinNotificationCategoryOption.hiddenPreviewShowTitle, + }, + ) + ]; + + /// Note: permissions aren't requested here just to demonstrate that can be + /// done later + final DarwinInitializationSettings initializationSettingsDarwin = + DarwinInitializationSettings( + requestAlertPermission: false, + requestBadgePermission: false, + requestSoundPermission: false, + notificationCategories: darwinNotificationCategories, + ); + + final InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin, + ); + + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: selectNotificationStream.add, + onDidReceiveBackgroundNotificationResponse: notificationTapBackground, + ); +} + void main() async { final settingsController = SettingsChangeProvider(); @@ -34,6 +167,8 @@ void main() async { } }); + setupPushNotification(); + await initMediaStorage(); dbProvider = DbProvider(); diff --git a/lib/src/views/chats/chat_list_view.dart b/lib/src/views/chats/chat_list_view.dart index 6ce12d2..1640c1e 100644 --- a/lib/src/views/chats/chat_list_view.dart +++ b/lib/src/views/chats/chat_list_view.dart @@ -16,7 +16,7 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/chats/chat_item_details_view.dart'; import 'package:twonly/src/views/home_view.dart'; import 'package:twonly/src/views/chats/media_viewer_view.dart'; -import 'package:twonly/src/views/profile_view.dart'; +import 'package:twonly/src/views/settings/settings_main_view.dart'; import 'package:twonly/src/views/chats/search_username_view.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/views/profile_view.dart b/lib/src/views/profile_view.dart deleted file mode 100644 index 5611bdc..0000000 --- a/lib/src/views/profile_view.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:provider/provider.dart'; -import 'package:twonly/src/model/json/user_data.dart'; -import 'package:restart_app/restart_app.dart'; -import 'package:flutter/material.dart'; -import 'package:twonly/src/providers/settings_change_provider.dart'; -import 'package:twonly/src/utils/storage.dart'; - -class ProfileView extends StatefulWidget { - const ProfileView({super.key}); - - // final SettingsController settingsController; - - @override - State createState() => _ProfileViewState(); -} - -class _ProfileViewState extends State { - final Future _userData = getUser(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: FutureBuilder( - future: _userData, - builder: (context, snap) { - if (snap.hasData) { - return Text("Settings"); - // return Text("Hello ${snap.data!.username}!"); - } else { - return Container(); - } - }, - ), - ), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - // Glue the SettingsController to the theme selection DropdownButton. - // - // When a user selects a theme from the dropdown list, the - // SettingsController is updated, which rebuilds the MaterialApp. - child: DropdownButton( - // Read the selected themeMode from the controller - value: context.watch().themeMode, - // Call the updateThemeMode method any time the user selects a theme. - onChanged: (theme) { - context.read().updateThemeMode(theme); - }, - items: const [ - DropdownMenuItem( - value: ThemeMode.system, - child: Text('System Theme'), - ), - DropdownMenuItem( - value: ThemeMode.light, - child: Text('Light Theme'), - ), - DropdownMenuItem( - value: ThemeMode.dark, - child: Text('Dark Theme'), - ) - ], - ), - ), - ElevatedButton( - onPressed: () { - showLicensePage(context: context); - }, - child: Text('Show Licenses'), - ), - FilledButton.icon( - onPressed: () async { - await deleteLocalUserData(); - Restart.restartApp( - notificationTitle: 'Successfully logged out', - notificationBody: 'Click here to open the app again', - ); - }, - label: Text("Logout"), - icon: Icon(Icons.no_accounts), - ), - ], - )); - } -} diff --git a/lib/src/views/settings/settings_main_view.dart b/lib/src/views/settings/settings_main_view.dart new file mode 100644 index 0000000..9577004 --- /dev/null +++ b/lib/src/views/settings/settings_main_view.dart @@ -0,0 +1,211 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:twonly/main.dart'; +import 'package:twonly/src/components/initialsavatar.dart'; +import 'package:twonly/src/model/json/user_data.dart'; +import 'package:restart_app/restart_app.dart'; +import 'package:flutter/material.dart'; +import 'package:twonly/src/providers/settings_change_provider.dart'; +import 'package:twonly/src/utils/storage.dart'; + +class ProfileView extends StatefulWidget { + const ProfileView({super.key}); + + @override + State createState() => _ProfileViewState(); +} + +class _ProfileViewState extends State { + UserData? userData; + + @override + void initState() { + super.initState(); + initAsync(); + } + + Future initAsync() async { + userData = await getUser(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Settings"), + ), + body: (userData == null) + ? null + : ListView( + children: [ + Padding( + padding: const EdgeInsets.all(30), + child: Row( + children: [ + InitialsAvatar( + displayName: userData!.username, + fontSize: 30, + ), + SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + userData!.displayName, + style: TextStyle(fontSize: 20), + textAlign: TextAlign.left, + ), + Text( + userData!.username, + style: TextStyle( + fontSize: 14, + ), + textAlign: TextAlign.left, + ), + ], + ), + ), + Align( + alignment: Alignment.centerRight, + child: IconButton( + onPressed: () {}, + icon: FaIcon(FontAwesomeIcons.qrcode), + ), + ) + ], + ), + ), + SettingsListTile( + icon: FontAwesomeIcons.user, + text: "Konto", + onTap: () {}, + ), + SettingsListTile( + icon: FontAwesomeIcons.shieldHeart, + text: "Subscription", + onTap: () {}, + ), + const Divider(), + SettingsListTile( + icon: FontAwesomeIcons.sun, + text: "Darstellung", + onTap: () {}, + ), + SettingsListTile( + icon: FontAwesomeIcons.lock, + text: "Datenschutz", + onTap: () {}, + ), + SettingsListTile( + icon: FontAwesomeIcons.bell, + text: "Benachrichtigungen", + onTap: () async { + const AndroidNotificationDetails + androidNotificationDetails = AndroidNotificationDetails( + '0', + 'Messages', + channelDescription: 'Messages from other users.', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker', + ); + const NotificationDetails notificationDetails = + NotificationDetails( + android: androidNotificationDetails); + await flutterLocalNotificationsPlugin.show(0, 'New message', + 'You got a new message from XX', notificationDetails, + payload: 'item x'); + }, + ), + const Divider(), + SettingsListTile( + icon: FontAwesomeIcons.circleQuestion, + text: "Help", + onTap: () {}, + ), + // Padding( + // padding: const EdgeInsets.all(16), + // // Glue the SettingsController to the theme selection DropdownButton. + // // + // // When a user selects a theme from the dropdown list, the + // // SettingsController is updated, which rebuilds the MaterialApp. + // child: DropdownButton( + // // Read the selected themeMode from the controller + // value: context.watch().themeMode, + // // Call the updateThemeMode method any time the user selects a theme. + // onChanged: (theme) { + // context + // .read() + // .updateThemeMode(theme); + // }, + // items: const [ + // DropdownMenuItem( + // value: ThemeMode.system, + // child: Text('System Theme'), + // ), + // DropdownMenuItem( + // value: ThemeMode.light, + // child: Text('Light Theme'), + // ), + // DropdownMenuItem( + // value: ThemeMode.dark, + // child: Text('Dark Theme'), + // ) + // ], + // ), + // ), + // ElevatedButton( + // onPressed: () { + // showLicensePage(context: context); + // }, + // child: Text('Show Licenses'), + // ), + // FilledButton.icon( + // onPressed: () async { + // await deleteLocalUserData(); + // Restart.restartApp( + // notificationTitle: 'Successfully logged out', + // notificationBody: 'Click here to open the app again', + // ); + // }, + // label: Text("Logout"), + // icon: Icon(Icons.no_accounts), + // ), + ], + ), + ); + } +} + +class SettingsListTile extends StatelessWidget { + final IconData icon; + final String text; + final VoidCallback onTap; + + const SettingsListTile({ + super.key, + required this.icon, + required this.text, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Padding( + padding: const EdgeInsets.only( + right: 10, + left: 19, + ), + child: FaIcon( + icon, + size: 20, + ), + ), + title: Text(text), + onTap: onTap, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 25c0208..cd125d8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -427,6 +427,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + url: "https://pub.dev" + source: hosted + version: "18.0.1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + url: "https://pub.dev" + source: hosted + version: "8.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -1167,6 +1191,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + timezone: + dependency: transitive + description: + name: timezone + sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d + url: "https://pub.dev" + source: hosted + version: "0.10.0" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b6b9fb..10b32d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: flutter: sdk: flutter flutter_image_compress: ^2.4.0 + flutter_local_notifications: ^18.0.1 flutter_localizations: sdk: flutter flutter_secure_storage: ^9.2.2 @@ -74,13 +75,6 @@ flutter: assets: # Add assets from the images directory to the application. - - assets/images/ - - assets/animations/present.lottie.json - - assets/animations/selfie2.json - - assets/animations/messages.json - - assets/animations/local.json - - assets/animations/test.json - - assets/animations/product.json - - assets/animations/twonlies.json - - assets/animations/rocket.json - - assets/animations/e2e.json + # - assets/images/ + # - assets/images/logo.jpg + - assets/