[READ-ONLY] Mirror of https://github.com/probablykasper/notifier. Android app for scheduling notifications
android app flutter notifications
0

Configure Feed

Select the types of activity you want to include in your feed.

Implement list page

Kasper (May 30, 2022, 7:16 PM +0200) ca1eb37a dfa5975e

+379 -100
+25
.vscode/launch.json
··· 1 + { 2 + // Use IntelliSense to learn about possible attributes. 3 + // Hover to view descriptions of existing attributes. 4 + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 + "version": "0.2.0", 6 + "configurations": [ 7 + { 8 + "name": "notifier", 9 + "request": "launch", 10 + "type": "dart" 11 + }, 12 + { 13 + "name": "notifier (profile mode)", 14 + "request": "launch", 15 + "type": "dart", 16 + "flutterMode": "profile" 17 + }, 18 + { 19 + "name": "notifier (release mode)", 20 + "request": "launch", 21 + "type": "dart", 22 + "flutterMode": "release" 23 + } 24 + ] 25 + }
+1 -1
android/app/build.gradle
··· 47 47 applicationId "com.example.notifier" 48 48 // You can update the following values to match your application needs. 49 49 // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 - minSdkVersion flutter.minSdkVersion 50 + minSdkVersion 21 51 51 targetSdkVersion flutter.targetSdkVersion 52 52 versionCode flutterVersionCode.toInteger() 53 53 versionName flutterVersionName
+148 -22
lib/list_page.dart
··· 1 + import 'package:flutter/widgets.dart'; 1 2 import 'package:get/get.dart' 2 - show Get, GetNavigation, GetxController, Inst, Obx, RxInt; 3 + show 4 + ExtensionDialog, 5 + Get, 6 + GetNavigation, 7 + GetxController, 8 + Inst, 9 + Obx, 10 + RxInt, 11 + StringExtension; 3 12 import 'package:flutter/material.dart' 4 13 show 5 - AlertDialog, 6 14 AppBar, 7 - BuildContext, 8 15 Center, 16 + Colors, 17 + Column, 18 + Container, 19 + CrossAxisAlignment, 20 + CustomScrollView, 21 + Divider, 22 + EdgeInsets, 9 23 ElevatedButton, 24 + Expanded, 25 + FlexibleSpaceBar, 10 26 FloatingActionButton, 27 + FontWeight, 11 28 Icon, 29 + IconButton, 12 30 Icons, 31 + InkWell, 32 + Row, 13 33 Scaffold, 34 + SliverAppBar, 35 + SliverChildListDelegate, 36 + SliverList, 14 37 StatelessWidget, 38 + Switch, 15 39 Text, 16 - ThemeMode, 17 - Widget, 18 - showDialog; 19 - import 'package:notifier/main.dart'; 40 + TextStyle, 41 + Theme, 42 + Widget; 43 + import 'package:get/state_manager.dart'; 44 + import 'notification_items.dart' show NotificationItem, Repeat; 45 + import 'theme.dart' show CustomTheme, toggleDarkMode; 20 46 21 47 class Controller extends GetxController { 22 - var count = RxInt(0); 23 - increment() => count++; 48 + var items = <NotificationItem>[].obs; 24 49 } 25 50 26 51 class ListPage extends StatelessWidget { ··· 30 55 final Controller c = Get.put(Controller()); 31 56 32 57 return Scaffold( 33 - // Use Obx(()=> to update Text() whenever count is changed. 34 - appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), 35 - 36 - // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context 37 - body: Center( 38 - child: ElevatedButton( 39 - child: const Text("Go to Other"), 40 - onPressed: () => Get.to(() => Other()))), 41 58 floatingActionButton: FloatingActionButton( 42 59 child: const Icon(Icons.add), 43 60 onPressed: () { 44 - toggleDarkMode(); 45 - })); 61 + print("Add notification item"); 62 + c.items.add(NotificationItem( 63 + title: 'Yo', 64 + description: 'Ddd', 65 + )); 66 + c.refresh(); 67 + }), 68 + body: CustomScrollView( 69 + slivers: <Widget>[ 70 + SliverAppBar( 71 + pinned: true, 72 + actions: <Widget>[ 73 + IconButton( 74 + icon: const Icon(Icons.wb_sunny), 75 + onPressed: () { 76 + print("Toggle dark mode"); 77 + toggleDarkMode(); 78 + }, 79 + color: Colors.white, 80 + ) 81 + ], 82 + floating: false, 83 + snap: false, 84 + expandedHeight: 200.0, 85 + backgroundColor: Theme.of(context).custom.appBarBackgroundColor, 86 + flexibleSpace: FlexibleSpaceBar( 87 + title: Text( 88 + 'Notifier', 89 + style: Theme.of(context).custom.appTitleStyle, 90 + ), 91 + titlePadding: const EdgeInsets.only(left: 72, bottom: 16), 92 + ), 93 + ), 94 + ListView(), 95 + ], 96 + )); 46 97 } 47 98 } 48 99 49 - class Other extends StatelessWidget { 100 + class ListView extends StatelessWidget { 50 101 // You can ask Get to find a Controller that is being used by another page and redirect you to it. 51 102 final Controller c = Get.find(); 52 103 104 + ListView(); 105 + 53 106 @override 54 107 Widget build(context) { 55 - // Access the updated count variable 56 - return Scaffold(body: Center(child: Text("${c.count}"))); 108 + return Obx( 109 + () => SliverList( 110 + delegate: SliverChildBuilderDelegate( 111 + childCount: c.items.length, 112 + (context, index) => InkWell( 113 + onTap: () async { 114 + // if (item['repeat'] != 'never' && 115 + // item['date'] < DateTime.now().millisecondsSinceEpoch) { 116 + // // make sure the date is not in the past for repeating notifications 117 + // print( 118 + // '[notifier] Running setNotifications() before opening edit dialog'); 119 + // await listModel.setNotifications(appIsOpen: true); 120 + // } 121 + print("[notifier] Opening edit dialog"); 122 + // return showDialog( 123 + // context: context, 124 + // builder: (BuildContext context) { 125 + // return NotificationDialog( 126 + // mode: 'edit', 127 + // // clone the item so it changes to it won't be saved 128 + // initialItem: Map<String, dynamic>.from(item), 129 + // listModel: listModel, 130 + // ); 131 + // }, 132 + // ); 133 + }, 134 + child: Column( 135 + children: <Widget>[ 136 + Container( 137 + padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), 138 + child: Row( 139 + children: <Widget>[ 140 + Container(width: 8), 141 + Expanded( 142 + child: Column( 143 + crossAxisAlignment: CrossAxisAlignment.start, 144 + children: [ 145 + Text( 146 + c.items[index].title, 147 + style: const TextStyle( 148 + fontSize: 16, 149 + fontWeight: FontWeight.w500, 150 + ), 151 + ), 152 + Text( 153 + c.items[index].description, 154 + style: const TextStyle( 155 + fontSize: 14, 156 + fontWeight: FontWeight.w500, 157 + // color: themeModel.descriptionColor, 158 + ), 159 + ), 160 + ], 161 + ), 162 + ), 163 + Switch( 164 + value: !c.items[index].disabled, 165 + onChanged: (bool newValue) { 166 + var updatedItem = c.items[index]; 167 + updatedItem.disabled = !newValue; 168 + c.items[index] = updatedItem; 169 + }, 170 + ), 171 + ], 172 + ), 173 + ), 174 + const Divider( 175 + height: 1, 176 + ), 177 + ], 178 + ), 179 + ), 180 + ), 181 + ), 182 + ); 57 183 } 58 184 }
+11 -45
lib/main.dart
··· 1 - import 'dart:isolate' show Isolate; 2 - import 'dart:ui'; 3 - import 'package:flutter/material.dart' 4 - show ThemeData, ThemeMode, WidgetsFlutterBinding, runApp; 5 - import 'package:flutter/scheduler.dart'; 6 - import 'theme.dart' show getTheme; 1 + import 'package:flutter/material.dart' show ThemeMode, runApp; 2 + // import 'package:notifier/notification_items.dart' show scheduleNotifications; 3 + import 'theme.dart' show getTheme, initializeTheme; 7 4 import 'list_page.dart' show ListPage; 8 5 import 'package:shared_preferences/shared_preferences.dart' 9 6 show SharedPreferences; 10 - import 'package:get/get.dart' show Get, GetMaterialApp, GetNavigation; 7 + import 'package:get/get.dart' show GetMaterialApp; 8 + // import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart' 9 + // show AndroidAlarmManager; 11 10 12 11 var prefsFuture = SharedPreferences.getInstance(); 13 12 14 13 void main() async { 14 + // Be sure to add this line if AndroidAlarmManager.initialize() call happens before runApp() 15 15 // WidgetsFlutterBinding.ensureInitialized(); 16 - // var prefs = prefsFuture.then((prefs) { 16 + 17 + // await AndroidAlarmManager.initialize(); 17 18 18 - // }) 19 - // var darkMode = prefs.getBool('darkMode') ?? true; 20 19 runApp( 21 20 GetMaterialApp( 22 21 title: 'Notifier', ··· 26 25 themeMode: ThemeMode.system, 27 26 home: ListPage()), 28 27 ); 29 - setupDarkMode(); 30 - } 31 - 32 - void setupDarkMode() async { 33 - var prefs = await prefsFuture; 34 - var darkMode = prefs.getBool("darkMode"); 35 - if (darkMode == null) { 36 - Get.changeThemeMode(ThemeMode.system); 37 - } else if (darkMode == true) { 38 - Get.changeThemeMode(ThemeMode.dark); 39 - } else { 40 - Get.changeThemeMode(ThemeMode.light); 41 - } 42 - } 43 - 44 - void setDarkMode(Brightness brightness) async { 45 - var systemBrightness = SchedulerBinding.instance.window.platformBrightness; 46 - var prefs = await prefsFuture; 47 - if (brightness == systemBrightness) { 48 - Get.changeThemeMode(ThemeMode.system); 49 - prefs.remove("darkMode"); 50 - } else if (brightness == Brightness.dark) { 51 - Get.changeThemeMode(ThemeMode.dark); 52 - prefs.setBool("darkMode", true); 53 - } else if (brightness == Brightness.light) { 54 - Get.changeThemeMode(ThemeMode.light); 55 - prefs.setBool("darkMode", false); 56 - } 57 - } 28 + initializeTheme(); 58 29 59 - void toggleDarkMode() async { 60 - if (Get.isDarkMode) { 61 - setDarkMode(Brightness.light); 62 - } else { 63 - setDarkMode(Brightness.dark); 64 - } 30 + // scheduleNotifications(); 65 31 }
+60
lib/notification_items.dart
··· 1 + // import 'package:awesome_notifications/awesome_notifications.dart' 2 + // show 3 + // AwesomeNotifications, 4 + // NotificationContent, 5 + // NotificationLayout, 6 + // NotificationCalendar; 7 + 8 + enum Repeat { 9 + never, 10 + daily, 11 + weekly, 12 + monthly, 13 + yearly, 14 + } 15 + 16 + class NotificationItem { 17 + String title; 18 + String description; 19 + bool disabled; 20 + Repeat repeat; 21 + // bool noSwipeAway; 22 + // int date; 23 + // int firedCount; 24 + 25 + NotificationItem({ 26 + this.title = '', 27 + this.description = '', 28 + this.disabled = false, 29 + this.repeat = Repeat.never, 30 + }); 31 + 32 + NotificationItem.fromJson(Map<String, dynamic> json) 33 + : title = json['title'], 34 + description = json['description'], 35 + disabled = json['disabled'], 36 + repeat = json['repeat']; 37 + 38 + Map<String, dynamic> toJson() => { 39 + 'title': title, 40 + 'description': description, 41 + 'disabled': disabled, 42 + 'repeat': repeat, 43 + }; 44 + } 45 + 46 + // void scheduleNotifications() async { 47 + // String localTimeZone = 48 + // await AwesomeNotifications().getLocalTimeZoneIdentifier(); 49 + // await AwesomeNotifications().createNotification( 50 + // content: NotificationContent( 51 + // id: id, 52 + // channelKey: 'scheduled', 53 + // title: 'Notification at exactly every single minute', 54 + // body: 55 + // 'This notification was schedule to repeat at every single minute at clock.', 56 + // notificationLayout: NotificationLayout.BigPicture, 57 + // bigPicture: 'asset://assets/images/melted-clock.png'), 58 + // schedule: NotificationCalendar( 59 + // month: 1, timeZone: localTimeZone, repeats: true)); 60 + // }
+119 -32
lib/theme.dart
··· 9 9 DialogTheme, 10 10 FloatingActionButtonThemeData, 11 11 RoundedRectangleBorder, 12 - ThemeData; 12 + TextStyle, 13 + ThemeData, 14 + ThemeMode; 15 + import 'package:flutter/scheduler.dart' show SchedulerBinding; 16 + import 'package:get/get.dart' show Get, GetNavigation; 17 + import 'main.dart' show prefsFuture; 18 + 19 + void initializeTheme() async { 20 + var prefs = await prefsFuture; 21 + var darkMode = prefs.getBool("darkMode"); 22 + if (darkMode == null) { 23 + Get.changeThemeMode(ThemeMode.system); 24 + } else if (darkMode == true) { 25 + Get.changeThemeMode(ThemeMode.dark); 26 + } else { 27 + Get.changeThemeMode(ThemeMode.light); 28 + } 29 + } 30 + 31 + void setDarkMode(Brightness brightness) async { 32 + var systemBrightness = Get.mediaQuery.platformBrightness; 33 + var prefs = await prefsFuture; 34 + if (brightness == systemBrightness) { 35 + Get.changeThemeMode(ThemeMode.system); 36 + prefs.remove("darkMode"); 37 + } else if (brightness == Brightness.dark) { 38 + Get.changeThemeMode(ThemeMode.dark); 39 + prefs.setBool("darkMode", true); 40 + } else if (brightness == Brightness.light) { 41 + Get.changeThemeMode(ThemeMode.light); 42 + prefs.setBool("darkMode", false); 43 + } 44 + } 45 + 46 + void toggleDarkMode() async { 47 + if (Get.isDarkMode) { 48 + setDarkMode(Brightness.light); 49 + } else { 50 + setDarkMode(Brightness.dark); 51 + } 52 + } 53 + 54 + class ThemedGray { 55 + final Color c1; 56 + final Color c2; 57 + final Color c3; 58 + final Color c4; 59 + final Color c5; 60 + final Color c6; 61 + final Color c7; 62 + final Color c8; 63 + final Color c9; 64 + const ThemedGray({ 65 + required this.c1, 66 + required this.c2, 67 + required this.c3, 68 + required this.c4, 69 + required this.c5, 70 + required this.c6, 71 + required this.c7, 72 + required this.c8, 73 + required this.c9, 74 + }); 75 + } 76 + 77 + const white = ThemedGray( 78 + c1: Color(0xFFF5F5F5), 79 + c2: Color(0xFFEEEEEE), 80 + c3: Color(0xFFE0E0E0), 81 + c4: Color(0xFFBDBDBD), 82 + c5: Color(0xFF9E9E9E), 83 + c6: Color(0xFF757575), 84 + c7: Color(0xFF616161), 85 + c8: Color(0xFF424242), 86 + c9: Color(0xFF212121), 87 + ); 88 + const grey = ThemedGray( 89 + c1: Color(0xFF121212), 90 + c2: Color(0xFF202124), 91 + c3: Color(0xFF313234), 92 + c4: Color(0xFF43454c), 93 + c5: Color(0xFF54565f), 94 + c6: Color(0xFF676974), 95 + c7: Color(0xFF9E9E9E), 96 + c8: Color(0xFFBDBDBD), 97 + c9: Color(0xFFF5F5F5), 98 + ); 99 + const blue = Color(0xFF00B0FF); 13 100 14 101 ThemeData getTheme(bool darkMode) { 15 - var grey = { 16 - 1: const Color(0xFF121212), 17 - 2: const Color(0xFF202124), 18 - 3: const Color(0xFF313234), 19 - 4: const Color(0xFF43454c), 20 - 5: const Color(0xFF54565f), 21 - 6: const Color(0xFF676974), 22 - 7: Colors.grey.shade500, 23 - 8: Colors.grey.shade400, 24 - 9: Colors.grey.shade100, 25 - }; 26 - var white = { 27 - 1: Colors.grey.shade100, 28 - 2: Colors.grey.shade200, 29 - 3: Colors.grey.shade300, 30 - 4: Colors.grey.shade400, 31 - 5: Colors.grey.shade500, 32 - 6: Colors.grey.shade600, 33 - 7: Colors.grey.shade700, 34 - 8: Colors.grey.shade800, 35 - 9: Colors.grey.shade900, 36 - }; 37 - const blue = Color(0xFF00B0FF); 38 102 var color = darkMode ? grey : white; 39 103 40 - var backgroundColor = color[1]; 41 - var splashColor = color[6]?.withOpacity(0.3); 42 - 43 104 return ThemeData( 44 105 fontFamily: 'Jost', 45 106 brightness: darkMode ? Brightness.dark : Brightness.light, 46 - scaffoldBackgroundColor: backgroundColor, 107 + scaffoldBackgroundColor: color.c1, 47 108 // textSelectionColor: Colors.white24, 48 109 // textSelectionHandleColor: blue, 49 110 // accentColor: Colors.white, 50 111 toggleableActiveColor: blue, 51 112 floatingActionButtonTheme: FloatingActionButtonThemeData( 52 113 foregroundColor: Colors.white, 53 - backgroundColor: darkMode ? color[3] : blue, 114 + backgroundColor: darkMode ? color.c3 : blue, 54 115 ), 55 116 // buttonColor: Colors.yellowAccent, 56 117 highlightColor: 57 - darkMode ? color[6]?.withOpacity(0.3) : color[6]?.withOpacity(0.2), 58 - splashColor: splashColor, 118 + darkMode ? color.c6.withOpacity(0.3) : color.c6.withOpacity(0.2), 119 + splashColor: color.c6.withOpacity(0.3), 59 120 buttonTheme: ButtonThemeData( 60 121 minWidth: 85, 61 122 height: 35, ··· 69 130 ), 70 131 ), 71 132 dialogTheme: DialogTheme( 72 - backgroundColor: darkMode ? color[2] : Colors.white, 133 + backgroundColor: darkMode ? color.c2 : Colors.white, 73 134 shape: RoundedRectangleBorder( 74 135 borderRadius: BorderRadius.circular(4), 75 136 ), 76 137 ), 77 138 ); 78 139 } 140 + 141 + class CustomThemeData { 142 + Color? appBarBackgroundColor; 143 + Color? descriptionColor; 144 + TextStyle? appTitleStyle; 145 + 146 + CustomThemeData light() { 147 + appBarBackgroundColor = blue; 148 + descriptionColor = white.c6; 149 + appTitleStyle = const TextStyle(color: Colors.white); 150 + return this; 151 + } 152 + 153 + CustomThemeData dark() { 154 + appBarBackgroundColor = grey.c2; 155 + descriptionColor = grey.c6; 156 + appTitleStyle = const TextStyle(color: blue); 157 + return this; 158 + } 159 + } 160 + 161 + extension CustomTheme on ThemeData { 162 + CustomThemeData get custom => brightness == Brightness.dark 163 + ? CustomThemeData().dark() 164 + : CustomThemeData().light(); 165 + }
+14
pubspec.lock
··· 8 8 url: "https://pub.dartlang.org" 9 9 source: hosted 10 10 version: "2.8.2" 11 + awesome_notifications: 12 + dependency: "direct main" 13 + description: 14 + name: awesome_notifications 15 + url: "https://pub.dartlang.org" 16 + source: hosted 17 + version: "0.6.21" 11 18 boolean_selector: 12 19 dependency: transitive 13 20 description: ··· 100 107 url: "https://pub.dartlang.org" 101 108 source: hosted 102 109 version: "4.6.5" 110 + intl: 111 + dependency: transitive 112 + description: 113 + name: intl 114 + url: "https://pub.dartlang.org" 115 + source: hosted 116 + version: "0.17.0" 103 117 js: 104 118 dependency: transitive 105 119 description:
+1
pubspec.yaml
··· 36 36 cupertino_icons: ^1.0.2 37 37 shared_preferences: ^2.0.15 38 38 get: ^4.6.5 39 + awesome_notifications: ^0.6.21 39 40 40 41 dev_dependencies: 41 42 flutter_test: