From a7935021c5b8d1df9e8d675f4fab9c0f89c1ea18 Mon Sep 17 00:00:00 2001 From: mahesh jamdade Date: Wed, 3 Jan 2024 20:08:29 +0530 Subject: [PATCH] show changelog in settings and on new release --- CHANGELOG.md | 3 + assets/CHANGELOG.md | 179 ++++++++++++++++++ ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/base_home.dart | 26 ++- lib/controller/app_controller.dart | 32 +++- lib/main.dart | 2 + lib/navbar/profile/settings.dart | 55 +++++- lib/themes/theme_selector.dart | 4 +- lib/widgets/whats_new.dart | 92 +++++++++ lib/widgets/widgets.dart | 16 +- pubspec.yaml | 3 +- 12 files changed, 390 insertions(+), 26 deletions(-) create mode 100644 assets/CHANGELOG.md create mode 100644 lib/widgets/whats_new.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index ad37915..75619bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ### 0.7.8+30 January 01, 2024 - Add Handsfree mode in explore +- Add ability to switch to classic mode +- User can now delete their account under edit profile +- Minor UI fixes and improvements ### 0.7.7+29 Sep 19, 2023 diff --git a/assets/CHANGELOG.md b/assets/CHANGELOG.md new file mode 100644 index 0000000..75619bb --- /dev/null +++ b/assets/CHANGELOG.md @@ -0,0 +1,179 @@ +### 0.7.8+30 January 01, 2024 + +- Add Handsfree mode in explore +- Add ability to switch to classic mode +- User can now delete their account under edit profile +- Minor UI fixes and improvements + +### 0.7.7+29 Sep 19, 2023 + +- Minor Fixes and Improvements + +### 0.7.6+28 August 18, 2023 + +- Fix Web view (Desktop) UI +- Add collections support for web +- Fix minor UI issues + +### 0.7.5+27 August 05, 2023 + +- New look for entire app with Ultra Gradients and Glassmorphism +- Minor UI fixes and improvements + +### 0.7.0+26 August 05, 2023 + +- Adds support for custom word collections Partially fixes [Issue 38](https://github.com/maheshmnj/vocabhub/issues/38) +- Pin words on the Dashboard +- Show notification time in user's timezone +- Initialize IOS notifications +- Show My Collections on Profile +- Minor UI tweaks on Profile page + +### 0.6.2+25 August 01, 2023 + +- Improve onboarding Animation +- Add push notifications for word of the day, new words +- Add ability to turn off push notifications + +### 0.6.1+24 July 30, 2023 + +- Adds support for push notifications + +### 0.6.0+23 July 25, 2023 + +- Edit History is now transparent and public +- Fix Responsiveness on desktop +- Save unpublished word to drafts +- Load word from drafts +- Add Granular Difference visualizer for word edits + +### 0.5.9+22 July 21, 2023 + +- Fix onboarding page +- And adds basic integration tests +- Fix app blocked at splashscreen when offline + +### 0.5.8+21 July 19, 2023 + +- Make User Profile publicly visible +- Add ability to see my past bug reports +- Add User onboarding + +### 0.5.7+20 July 16, 2023 + +- Use RiverPod for user state management +- Fix login state maintain + +### 0.5.6+19 July 04, 2023 + +- Swipe up to see more words in explore page +- Search Page now shows words by alphabets + +### 0.5.5+18 July 04, 2023 + +- Fixes:[https://github.com/maheshmnj/vocabhub/issues/30](Issue: 30) word of the day publish logic. + +### 0.5.4+17 July 02, 2023 + +- Tap explore tab to scroll to top +- fix Word detail and explore UI overflow + +### 0.5.3+16 Jun 25, 2023 + +- Load explore when offline. +- hadle network error for profile page +- Fetch words from local storage when offline + +### 0.5.2+15 Jun 21, 2023 + +- Fix workflow Keys not passed to build release. + +### 0.5.1+14 Jun 11, 2023 + +- Improve explore page logic for new words +- Add setting to hide/unhide words in explore page. +- Get Notified for word of the day + +### 0.5.0+13 Jun 11, 2023 + +- Migrate app and components to Material 3.0 +- Add support for changing theme +- Adds support for dark mode +- Refactor search implementation +- Adds Ask for user rating on Play Store + +### 0.4.2+12 May 28, 2023 + +- Fix minor issues + +### 0.4.1+11 May 22, 2023 + +- Fix: Google sign In fails [Issue 23](https://github.com/maheshmnj/vocabhub/issues/23) +- Skip signin banner + +### 0.4.0 (11) + +- Fix: [Issue 11](https://github.com/maheshmnj/vocabhub/issues/11) +- Redesigned public release with minimal features + +### 0.3.4 (9) + +- Internal Release (redesigned) + +### 0.3.4 + +- Adds Google Sign in support +- edits restricted to signed-in users + +### 0.3.3 + +- Add dark mode animation + +### 0.3.2 + +- fix drawer navigation +- Improve Appbar layout for tablet + +### 0.3.1 + +- Shuffle word list +- adds support for mnemonics +- Improve color theme +- fix minor UI issues + +### 0.3.0 + +- Adds Edit Mode feature +- Improved consistency across mobile and web. + +### 0.2.3 + +- Adds Analytics for better stats. +- minor fixes + +### 0.2.2 + +- Maintain Dark mode state +- Show total number of words. + +### 0.2.1 + +- Add Drawer and app bar +- Add Example sentences +- Change color scheme to green + +### 0.2.0 + +- Improve color schemes +- Add text animation +- Improve responsiveness +- Improved search. + +### 0.1.2 + +- Add dark mode +- improve minor bugs + +### 0.1.1 + +- Initial functional app diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 17b75f5..dc277e0 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -158,7 +158,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6..b52b2e6 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { return; } try { + final appController = ref.read(appProvider); + final packageInfo = await PackageInfo.fromPlatform(); final String appVersion = packageInfo.version; final int appBuildNumber = int.parse(packageInfo.buildNumber); + final remoteConfig = FirebaseRemoteConfig.instance; await remoteConfig.setConfigSettings(RemoteConfigSettings( fetchTimeout: const Duration(minutes: 1), @@ -80,7 +85,9 @@ class _AdaptiveLayoutState extends ConsumerState { await remoteConfig.fetchAndActivate(); final version = remoteConfig.getString('${Constants.VERSION_KEY}'); final buildNumber = remoteConfig.getInt('${Constants.BUILD_NUMBER_KEY}'); - final appController = ref.watch(appProvider); + + final oldVersion = appController.version.split(' ')[0]; + final oldBuildNumber = int.parse(appController.version.split(' ')[1]); if (appVersion != version || buildNumber > appBuildNumber) { ref .read(appProvider.notifier) @@ -90,9 +97,18 @@ class _AdaptiveLayoutState extends ConsumerState { launchUrl(Uri.parse(Constants.PLAY_STORE_URL), mode: LaunchMode.externalApplication); }); } else { - ref - .read(appProvider.notifier) - .copyWith(appController.copyWith(showFAB: true, extended: true, hasUpdate: false)); + if (oldVersion != version || oldBuildNumber < buildNumber) { + Navigate.push( + context, + WhatsNew(), + transitionType: TransitionType.btt, + ); + ref.read(appProvider.notifier).copyWith(appController.copyWith( + showFAB: true, + extended: true, + hasUpdate: false, + version: '$appVersion $appBuildNumber')); + } } } catch (_) { setState(() {}); @@ -116,7 +132,7 @@ class _AdaptiveLayoutState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) { NavbarNotifier.showSnackBar(context, message, actionLabel: action, - bottom: 50, + bottom: Platform.isIOS ? 50 : kNavbarHeight * 1.2, onActionPressed: onActionPressed, duration: persist ? Duration(days: 1) : Duration(seconds: 3), onClosed: () { if (mounted) { diff --git a/lib/controller/app_controller.dart b/lib/controller/app_controller.dart index e5012b3..0997ed6 100644 --- a/lib/controller/app_controller.dart +++ b/lib/controller/app_controller.dart @@ -9,12 +9,14 @@ class AppController { final bool showFAB; final bool extended; final bool hasUpdate; + final String version; AppController({ this.index = 0, this.showFAB = true, this.extended = true, this.hasUpdate = false, + this.version = '1.0.0 1', }); AppController copyWith({ @@ -22,12 +24,14 @@ class AppController { bool? showFAB, bool? extended, bool? hasUpdate, + String? version, }) { return AppController( index: index ?? this.index, showFAB: showFAB ?? this.showFAB, extended: extended ?? this.extended, hasUpdate: hasUpdate ?? this.hasUpdate, + version: version ?? this.version, ); } @@ -38,7 +42,7 @@ class AppController { result.addAll({'showFAB': showFAB}); result.addAll({'extended': extended}); result.addAll({'hasUpdate': hasUpdate}); - + result.addAll({'version': version}); return result; } @@ -48,6 +52,7 @@ class AppController { showFAB: map['showFAB'] ?? false, extended: map['extended'] ?? false, hasUpdate: map['hasUpdate'] ?? false, + version: map['version'] ?? '1.0.0 1', ); } @@ -57,7 +62,7 @@ class AppController { @override String toString() { - return 'AppController(index: $index, showFAB: $showFAB, extended: $extended, hasUpdate: $hasUpdate)'; + return 'AppController(index: $index, showFAB: $showFAB, extended: $extended, hasUpdate: $hasUpdate, version: $version)'; } @override @@ -68,12 +73,17 @@ class AppController { other.index == index && other.showFAB == showFAB && other.extended == extended && - other.hasUpdate == hasUpdate; + other.hasUpdate == hasUpdate && + other.version == version; } @override int get hashCode { - return index.hashCode ^ showFAB.hashCode ^ extended.hashCode ^ hasUpdate.hashCode; + return index.hashCode ^ + showFAB.hashCode ^ + extended.hashCode ^ + hasUpdate.hashCode ^ + version.hashCode; } } @@ -96,12 +106,16 @@ class AppNotifier extends StateNotifier { state = state.copyWith(hasUpdate: update); } + void setVersion(String version) { + state = state.copyWith(version: version); + } + void copyWith(AppController appController) { state = state.copyWith( - index: appController.index, - showFAB: appController.showFAB, - extended: appController.extended, - hasUpdate: appController.hasUpdate - ); + index: appController.index, + showFAB: appController.showFAB, + extended: appController.extended, + hasUpdate: appController.hasUpdate, + version: appController.version); } } diff --git a/lib/main.dart b/lib/main.dart index 2543983..de8bf14 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,6 +28,7 @@ import 'package:vocabhub/themes/theme_utils.dart'; import 'package:vocabhub/themes/vocabtheme_controller.dart'; import 'package:vocabhub/utils/firebase_options.dart'; import 'package:vocabhub/utils/logger.dart'; +import 'package:vocabhub/widgets/whats_new.dart'; import 'constants/constants.dart'; @@ -220,6 +221,7 @@ class _VocabAppState extends ConsumerState { AboutVocabhub.route: (context) => AboutVocabhub(), SettingsPage.route: (context) => SettingsPage(), ViewBugReports.route: (context) => ViewBugReports(), + WhatsNew.route: (context) => WhatsNew(), }, themeMode: appThemeController.isDark ? ThemeMode.dark : ThemeMode.light, home: SplashScreen(), diff --git a/lib/navbar/profile/settings.dart b/lib/navbar/profile/settings.dart index d9b3e08..b9317a2 100644 --- a/lib/navbar/profile/settings.dart +++ b/lib/navbar/profile/settings.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:navbar_router/navbar_router.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:vocabhub/exports.dart'; import 'package:vocabhub/models/user.dart'; @@ -15,6 +16,7 @@ import 'package:vocabhub/themes/theme_selector.dart'; import 'package:vocabhub/widgets/button.dart'; import 'package:vocabhub/widgets/drawer.dart'; import 'package:vocabhub/widgets/responsive.dart'; +import 'package:vocabhub/widgets/whats_new.dart'; import 'package:vocabhub/widgets/widgets.dart'; class SettingsPage extends StatefulWidget { @@ -87,6 +89,10 @@ class _SettingsPageMobileState extends ConsumerState { )); } + Future getVersion() async { + return await PackageInfo.fromPlatform(); + } + @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; @@ -97,6 +103,7 @@ class _SettingsPageMobileState extends ConsumerState { user.setUser(userNew); } }); + final appController = ref.read(appProvider); return Material( color: Colors.transparent, @@ -114,6 +121,51 @@ class _SettingsPageMobileState extends ConsumerState { }, ), hLine(), + FutureBuilder( + future: getVersion(), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.data == null) return SizedBox.shrink(); + final String appVersion = snapshot.data!.version; + final int appBuildNumber = int.parse(snapshot.data!.buildNumber); + final oldVersion = appController.version.split(' ')[0]; + final oldBuildNumber = int.parse(appController.version.split(' ')[1]); + + if (appVersion != oldVersion || oldBuildNumber < appBuildNumber) { + return settingTile( + 'Whats New', + title: RichText( + text: TextSpan(children: [ + TextSpan( + text: 'Whats', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: colorScheme.primary, + )), + TextSpan( + text: ' New', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: colorScheme.primary, + )), + TextSpan( + text: ' ✨', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: colorScheme.primary, + )), + ])), + onTap: () { + Navigate.pushNamed(context, isRootNavigator: true, WhatsNew.route); + }, + ); + } else { + return SizedBox.shrink(); + } + }), + hLine(), 10.0.vSpacer(), Padding( padding: 16.0.horizontalPadding, @@ -140,7 +192,7 @@ class _SettingsPageMobileState extends ConsumerState { ), ), Padding( - padding: 16.0.horizontalPadding, + padding: 16.0.horizontalPadding + 10.0.verticalPadding, child: Row( children: [ Text('Use Classic Theme', @@ -171,6 +223,7 @@ class _SettingsPageMobileState extends ConsumerState { ], ), ), + 20.0.vSpacer(), ThemeSelector( value: appTheme.themeSeed, onThemeChanged: (val) { diff --git a/lib/themes/theme_selector.dart b/lib/themes/theme_selector.dart index e50ebd0..976d5c1 100644 --- a/lib/themes/theme_selector.dart +++ b/lib/themes/theme_selector.dart @@ -57,7 +57,9 @@ class _ThemeSelectorState extends State { for (var color in widget.colors) GestureDetector( onTap: () { - widget.onThemeChanged(color); + if (widget.value.value != color.value) { + widget.onThemeChanged(color); + } }, child: Padding( padding: 12.0.horizontalPadding, diff --git a/lib/widgets/whats_new.dart b/lib/widgets/whats_new.dart new file mode 100644 index 0000000..07d58c5 --- /dev/null +++ b/lib/widgets/whats_new.dart @@ -0,0 +1,92 @@ +// Create a stateful widget called WhatsNew +// This widget is shown when the app is opened for the first time +// after the update + +import 'package:flutter/material.dart'; +import 'package:vocabhub/utils/utils.dart'; +import 'package:vocabhub/widgets/widgets.dart'; + +class WhatsNew extends StatefulWidget { + static const route = '/whats-new'; + @override + _WhatsNewState createState() => _WhatsNewState(); +} + +class _WhatsNewState extends State { + Future>> loadAsset() async { + final result = await DefaultAssetBundle.of(context).loadString('assets/CHANGELOG.md'); + List> changelog = []; + List release = []; + final lines = result.split('\n'); + for (int i = 0; i < lines.length; i++) { + if (lines[i].isEmpty) continue; + if (lines[i].startsWith('###')) { + if (release.isNotEmpty) { + changelog.add(release); + release = []; + } + release.add(lines[i].substring(4)); + } else { + release.add(lines[i]); + } + } + print(changelog[1]); + return changelog; + } + + @override + void initState() { + super.initState(); + // Call the method that shows the dialog + WidgetsBinding.instance.addPostFrameCallback((_) { + loadAsset(); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + child: SafeArea( + child: Column(children: [ + SizedBox(height: 10), + Padding( + padding: 16.0.horizontalPadding, + child: Row( + children: [ + Text('What\'s New', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + Spacer(), + IconButton(onPressed: () => Navigator.pop(context), icon: Icon(Icons.close)) + ], + ), + ), + SizedBox(height: 10), + Expanded( + child: FutureBuilder>>( + future: loadAsset(), + builder: (context, AsyncSnapshot>> snapshot) { + if (snapshot.data == null) { + return LoadingWidget(); + } + return ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + final release = snapshot.data![index]; + return Column( + children: [ + for (int i = 0; i < release.length; i++) + ListTile( + leading: i == 0 ? Icon(Icons.new_releases_rounded) : null, + title: Text(release[i], + style: TextStyle( + fontSize: i == 0 ? 18 : 16, + fontWeight: i == 0 ? FontWeight.bold : FontWeight.normal, + ))), + ], + ); + }); + })) + ]), + ), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 2406e8e..538838b 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -226,18 +226,20 @@ Widget settingTile(String label, Function? onTap, IconData? trailingIcon, IconData? leadingIcon, + Widget? title, double titleSize = 18, double verticalPadding = 24.0}) { return ListTile( leading: leadingIcon != null ? Icon(leadingIcon) : null, minVerticalPadding: verticalPadding, - title: Text( - '$label', - style: TextStyle( - fontSize: titleSize, - fontWeight: FontWeight.w400, - ), - ), + title: title ?? + Text( + '$label', + style: TextStyle( + fontSize: titleSize, + fontWeight: FontWeight.w400, + ), + ), subtitle: description != null ? Text( '$description', diff --git a/pubspec.yaml b/pubspec.yaml index c50e6c4..20eed9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: navbar_router: ^0.7.3 # navbar_router: # path: ../../oss/my_packages/navbar_router - # url: https://github.com/maheshmnj/navbar_router.git + # url: https://github.com/maheshmnj/navbar_router.git package_info_plus: ^4.0.1 pull_to_refresh: ^2.0.0 realtime_client: ^0.1.15 @@ -71,3 +71,4 @@ flutter: - assets/rive/ - .env - shorebird.yaml + - CHANGELOG.md