diff --git a/README.md b/README.md
index fafeec0d..2a33c277 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ The app is free, forever: no ads, no spam, no need to sign up or pay.
Medito App is a flutter project available on Android and iOS maintained by the Medito Foundation and the community.
+
## Download the app
- Play Store: https://play.google.com/store/apps/details?id=meditofoundation.medito
- App Store: https://apps.apple.com/us/app/medito/id1500780518
@@ -15,6 +16,13 @@ Medito App is a flutter project available on Android and iOS maintained by the M
NOTE: If you install Medito app using APK file, please make sure to verify that the APK file is signed by Medito Foundation. See [VERIFY_APK](VERIFY_APK.md) for more information.
+## Install
+[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/meditohq/medito-app?label=latest%20version&sort=semver)](https://github.com/meditohq/medito-app/releases)
+
+| Android | iOS |
+| :--: | :--: |
+|
| |
+
## How to use this code
The best way to start is by opening the project with [Android Studio](https://developer.android.com/studio) or [Visual Studio](https://visualstudio.microsoft.com/)
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 186b7155..3c4101c3 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh
index 3510029c..f7a1fa03 100755
--- a/ios/Flutter/flutter_export_environment.sh
+++ b/ios/Flutter/flutter_export_environment.sh
@@ -1,14 +1,14 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
-export "FLUTTER_ROOT=/Users/mike/Development/flutter"
-export "FLUTTER_APPLICATION_PATH=/Users/mike/Projects/medito-app"
-export "FLUTTER_TARGET=/Users/mike/Projects/medito-app/lib/main.dart"
+export "FLUTTER_ROOT=C:\src\flutter\flutter"
+export "FLUTTER_APPLICATION_PATH=C:\Users\Harshad\Desktop\Workspace\medito-app"
+export "COCOAPODS_PARALLEL_CODE_SIGN=true"
+export "FLUTTER_TARGET=lib\main.dart"
export "FLUTTER_BUILD_DIR=build"
-export "SYMROOT=${SOURCE_ROOT}/../build/ios"
-export "FLUTTER_BUILD_NAME=2.0.16"
-export "FLUTTER_BUILD_NUMBER=20016"
-export "DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue"
+export "SYMROOT=${SOURCE_ROOT}/../build\ios"
+export "FLUTTER_BUILD_NAME=2.0.24"
+export "FLUTTER_BUILD_NUMBER=20024"
export "DART_OBFUSCATION=false"
-export "TRACK_WIDGET_CREATION=true"
+export "TRACK_WIDGET_CREATION=false"
export "TREE_SHAKE_ICONS=false"
-export "PACKAGE_CONFIG=/Users/mike/Projects/medito-app/.dart_tool/package_config.json"
+export "PACKAGE_CONFIG=.packages"
diff --git a/lib/audioplayer/audio_player_service.dart b/lib/audioplayer/audio_player_service.dart
index 1dd6d9d5..3a2aa2b1 100644
--- a/lib/audioplayer/audio_player_service.dart
+++ b/lib/audioplayer/audio_player_service.dart
@@ -14,6 +14,7 @@ import 'package:just_audio/just_audio.dart';
const fadeDuration = 20;
const PLAY_BG_SOUND = 'play_bg_sound';
const SEND_BG_SOUND = 'send_bg_sound';
+const INIT_BG_SOUND = 'init_bg_sound';
const SET_BG_SOUND_VOL = 'set_bg_sound_vol';
/// This task defines logic for playing a list of podcast episodes.
@@ -24,11 +25,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
StreamSubscription _eventSubscription;
var _duration = Duration();
- var _currentlyPlayingBGSound;
+ var _currentlyPlayingBGSound = '';
int get index => _player.currentIndex;
MediaItem mediaItem;
- var initialBgVolume = 0.6;
+ var initialBgVolume = 0.4;
var _updatedStats = false;
@override
@@ -132,7 +133,9 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future onPlay() {
- _bgPlayer.play();
+ if(_currentlyPlayingBGSound.isNotEmptyAndNotNull()) {
+ _bgPlayer.play();
+ }
return _player.play();
}
@@ -156,10 +159,12 @@ class AudioPlayerTask extends BackgroundAudioTask {
await _player.stop();
await _broadcastState();
break;
+ case INIT_BG_SOUND:
+ AudioServiceBackground.sendCustomEvent(
+ {SEND_BG_SOUND: _currentlyPlayingBGSound});
+ break;
case SEND_BG_SOUND:
- if ((params as String).isNotEmptyAndNotNull()) {
- _currentlyPlayingBGSound = params;
- }
+ _currentlyPlayingBGSound = params ?? '';
AudioServiceBackground.sendCustomEvent(
{SEND_BG_SOUND: _currentlyPlayingBGSound});
break;
diff --git a/lib/network/downloads/downloads_bloc.dart b/lib/network/downloads/downloads_bloc.dart
index ab054460..ec6e726f 100644
--- a/lib/network/downloads/downloads_bloc.dart
+++ b/lib/network/downloads/downloads_bloc.dart
@@ -80,4 +80,13 @@ class DownloadsBloc {
list.removeWhere((element) => MediaItem.fromJson(jsonDecode(element)).id == mediaFile.id);
await prefs.setStringList(savedFilesKey, list);
}
+
+ // This method is used to save the updated order of the list of downloaded sessions
+ /// Saves a given `List` of `MediaItem` elements (downloaded sessions)
+ static Future saveDownloads(List mediaList) async {
+ var prefs = await SharedPreferences.getInstance();
+ var list =
+ mediaList.map((MediaItem mediaItem) => jsonEncode(mediaItem)).toList();
+ await prefs.setStringList(savedFilesKey, list);
+ }
}
diff --git a/lib/utils/strings.dart b/lib/utils/strings.dart
index f0d45fa8..2db7e003 100644
--- a/lib/utils/strings.dart
+++ b/lib/utils/strings.dart
@@ -2,7 +2,8 @@ const String DOWNLOADS = 'Downloads';
const String DOWNLOAD = 'Download';
const String SOUNDS = 'Sounds';
const String BEGIN = 'Begin';
-const String PICK_NARRATOR = 'PICK A NARRATOR & DURATION';
+const String PICK_NARRATOR_AND_DURATION = 'Pick a narrator & duration';
+const String PICK_DURATION = 'Pick a duration';
const String SHOW_DOWNLOADS = 'Show downloads';
const String FAVOURITES = 'Favouites';
const String EMPTY_DOWNLOADS_MESSAGE =
diff --git a/lib/widgets/btm_nav/downloads_widget.dart b/lib/widgets/btm_nav/downloads_widget.dart
index ada38c10..bd12d86b 100644
--- a/lib/widgets/btm_nav/downloads_widget.dart
+++ b/lib/widgets/btm_nav/downloads_widget.dart
@@ -40,20 +40,29 @@ class _DownloadsListWidgetState extends State
hasCloseButton: true,
),
key: scaffoldKey,
- body: _downloadList.isEmpty
- ? _getEmptyWidget()
- : _getDownloadList(),
+ body: _downloadList.isEmpty ? _getEmptyWidget() : _getDownloadList(),
);
}
Widget _getDownloadList() {
- return ListView.builder(
- padding: EdgeInsets.symmetric(vertical: 8),
- itemCount: _downloadList.length,
- itemBuilder: (context, i) {
- var item = _downloadList[i];
- return _getSlidingItem(item, context);
+ // In order for the Dismissible action still to work on the list items,
+ // the default ReorderableListView is used (instead of the .builder one)
+ return ReorderableListView(
+ padding: EdgeInsets.symmetric(vertical: 8),
+ onReorder: (int oldIndex, int newIndex) {
+ setState(() {
+ if (oldIndex < newIndex) {
+ newIndex -= 1;
+ }
+ var reorderedItem = _downloadList.removeAt(oldIndex);
+ _downloadList.insert(newIndex, reorderedItem);
+ // To ensure, that the new list order is saved
+ DownloadsBloc.saveDownloads(_downloadList);
});
+ },
+ children:
+ _downloadList.map((item) => _getSlidingItem(item, context)).toList(),
+ );
}
Widget _getEmptyWidget() => EmptyStateWidget(
@@ -67,6 +76,8 @@ class _DownloadsListWidgetState extends State
Widget _getSlidingItem(MediaItem item, BuildContext context) {
return InkWell(
+ // This (additional) key is required in order for the ReorderableListView to distinguish between the different list items
+ key: ValueKey(item.id),
onTap: () {
_openPlayer(item, context);
},
diff --git a/lib/widgets/header_widget.dart b/lib/widgets/header_widget.dart
index cb3390cb..c6e2ccf3 100644
--- a/lib/widgets/header_widget.dart
+++ b/lib/widgets/header_widget.dart
@@ -24,15 +24,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class HeaderWidget extends StatelessWidget {
- HeaderWidget({Key key,
+ HeaderWidget({
+ Key key,
this.primaryColorController,
this.titleController,
this.coverController,
this.backgroundImageController,
this.descriptionController,
this.whiteText = false,
- })
- : super(key: key);
+ }) : super(key: key);
final StreamController primaryColorController;
final StreamController titleController;
final StreamController> coverController;
@@ -66,15 +66,11 @@ class HeaderWidget extends StatelessWidget {
children: [
MeditoAppBarWidget(transparent: true),
_getRow(),
- Container(height: whiteText ? 0 : 16)
],
),
],
),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0),
- child: _getDescriptionWidget(),
- )
+ _getDescriptionWidget(),
],
),
);
@@ -123,21 +119,17 @@ class HeaderWidget extends StatelessWidget {
);
}
- StreamBuilder _getTitleStream() =>
- StreamBuilder(
- initialData: '',
- stream: titleController.stream,
- builder: (context, snapshot) {
- return Text(
- snapshot.data,
- maxLines: 3,
- overflow: TextOverflow.ellipsis,
- style: Theme
- .of(context)
- .textTheme
- .headline1,
- );
- });
+ StreamBuilder _getTitleStream() => StreamBuilder(
+ initialData: '',
+ stream: titleController.stream,
+ builder: (context, snapshot) {
+ return Text(
+ snapshot.data,
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.headline1,
+ );
+ });
StreamBuilder> _actualImageStream() {
return StreamBuilder>(
@@ -153,7 +145,7 @@ class HeaderWidget extends StatelessWidget {
child: CircularProgressIndicator(
backgroundColor: MeditoColors.transparent,
valueColor:
- AlwaysStoppedAnimation(MeditoColors.darkMoon),
+ AlwaysStoppedAnimation(MeditoColors.darkMoon),
)),
);
break;
@@ -194,7 +186,8 @@ class HeaderWidget extends StatelessWidget {
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.isNotEmptyAndNotNull()) {
return Padding(
- padding: const EdgeInsets.only(bottom: 20.0),
+ padding:
+ const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
child: Markdown(
data: snapshot?.data ?? '',
onTapLink: _linkTap,
@@ -202,20 +195,16 @@ class HeaderWidget extends StatelessWidget {
physics: NeverScrollableScrollPhysics(),
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context))
.copyWith(
- p: Theme
- .of(context)
- .textTheme
- .subtitle1
- .copyWith(
- fontSize: 14.0,
- color: whiteText
- ? MeditoColors.walterWhite
- : MeditoColors.meditoTextGrey)),
+ p: Theme.of(context).textTheme.subtitle1.copyWith(
+ fontSize: 14.0,
+ color: whiteText
+ ? MeditoColors.walterWhite
+ : MeditoColors.meditoTextGrey)),
shrinkWrap: true,
),
);
} else {
- return Container();
+ return Container(height: 20);
}
});
}
diff --git a/lib/widgets/home/small_shortcuts_row_widget.dart b/lib/widgets/home/small_shortcuts_row_widget.dart
index 15e9b415..e4513ad8 100644
--- a/lib/widgets/home/small_shortcuts_row_widget.dart
+++ b/lib/widgets/home/small_shortcuts_row_widget.dart
@@ -18,7 +18,7 @@ class SmallShortcutsRowWidget extends StatefulWidget {
class SmallShortcutsRowWidgetState extends State {
final _bloc = ShortcutsBloc();
-
+ bool isLandscape = false;
@override
void initState() {
super.initState();
@@ -31,46 +31,52 @@ class SmallShortcutsRowWidgetState extends State {
@override
Widget build(BuildContext context) {
- return SizeChangedLayoutNotifier(
- child: StreamBuilder>(
- stream: _bloc.shortcutList.stream,
- initialData: ApiResponse.loading(),
- builder: (context, snapshot) {
- switch (snapshot.data.status) {
- case Status.LOADING:
- return _getLoadingWidget();
- break;
- case Status.COMPLETED:
- return GridView.count(
- crossAxisCount: 2,
- padding:
- const EdgeInsets.only(left: 12.0, right: 12.0, top: 8.0),
- scrollDirection: Axis.vertical,
- childAspectRatio: 2.6,
- physics: NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- children: List.generate(snapshot.data.body?.data?.length ?? 0,
- (index) {
- return Card(
- clipBehavior: Clip.antiAlias,
- color: MeditoColors.deepNight,
- child: SmallShortcutWidget(
- snapshot.data.body.data[index], widget.onTap),
+ return OrientationBuilder(builder: (context, orientation) {
+ if (MediaQuery.of(context).size.width > 600) {
+ isLandscape = true;
+ } else {
+ isLandscape = false;
+ }
+ return SizeChangedLayoutNotifier(
+ child: StreamBuilder>(
+ stream: _bloc.shortcutList.stream,
+ initialData: ApiResponse.loading(),
+ builder: (context, snapshot) {
+ switch (snapshot.data.status) {
+ case Status.LOADING:
+ return _getLoadingWidget();
+ break;
+ case Status.COMPLETED:
+ return GridView.count(
+ crossAxisCount: isLandscape ? 4 : 2,
+ padding: const EdgeInsets.only(
+ left: 12.0, right: 12.0, top: 8.0),
+ scrollDirection: Axis.vertical,
+ childAspectRatio: 2.6,
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ children: List.generate(
+ snapshot.data.body?.data?.length ?? 0, (index) {
+ return Card(
+ clipBehavior: Clip.antiAlias,
+ color: MeditoColors.deepNight,
+ child: SmallShortcutWidget(
+ snapshot.data.body.data[index], widget.onTap),
+ );
+ }),
);
- }),
- );
- break;
- case Status.ERROR:
- return Icon(Icons.error);
- break;
- }
- return Container();
- }),
- );
+ break;
+ case Status.ERROR:
+ return Icon(Icons.error);
+ break;
+ }
+ return Container();
+ }));
+ });
}
Widget _getLoadingWidget() => GridView.count(
- crossAxisCount: 2,
+ crossAxisCount: isLandscape ? 4 : 2,
padding: const EdgeInsets.only(left: 12.0, right: 12.0, top: 8.0),
scrollDirection: Axis.vertical,
childAspectRatio: 2.6,
diff --git a/lib/widgets/player/background_sounds_sheet_widget.dart b/lib/widgets/player/background_sounds_sheet_widget.dart
index 032d9514..42a707f5 100644
--- a/lib/widgets/player/background_sounds_sheet_widget.dart
+++ b/lib/widgets/player/background_sounds_sheet_widget.dart
@@ -26,7 +26,6 @@ import 'package:Medito/widgets/player/position_indicator_widget.dart';
import 'package:audio_service/audio_service.dart';
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:flutter/material.dart';
-import 'package:pedantic/pedantic.dart';
import 'package:rxdart/rxdart.dart';
class ChooseBackgroundSoundDialog extends StatefulWidget {
@@ -68,7 +67,7 @@ class _ChooseBackgroundSoundDialogState
volume = await retrieveSavedBgVolume();
_dragBgVolumeSubject.add(volume);
await AudioService.customAction(SET_BG_SOUND_VOL, volume / 100);
- await AudioService.customAction(SEND_BG_SOUND);
+ await AudioService.customAction(INIT_BG_SOUND, "");
}
}
@@ -80,7 +79,7 @@ class _ChooseBackgroundSoundDialogState
builder: (context, snapshot) {
var currentSounds;
try {
- currentSounds = snapshot.data[SEND_BG_SOUND] ?? NONE;
+ currentSounds = (snapshot.data[SEND_BG_SOUND] as String).isNotEmptyAndNotNull() ? snapshot.data[SEND_BG_SOUND] : NONE;
} catch (e) {
currentSounds = NONE;
}
@@ -222,7 +221,7 @@ class _ChooseBackgroundSoundDialogState
);
void _noneSelected() {
- AudioService.customAction(SEND_BG_SOUND, NONE);
+ AudioService.customAction(SEND_BG_SOUND, '');
AudioService.customAction(PLAY_BG_SOUND, '');
addBgSoundSelectionToSharedPrefs('', '');
}
diff --git a/lib/widgets/session_options/session_options_screen.dart b/lib/widgets/session_options/session_options_screen.dart
index 355a0d1a..3cdd3785 100644
--- a/lib/widgets/session_options/session_options_screen.dart
+++ b/lib/widgets/session_options/session_options_screen.dart
@@ -13,6 +13,8 @@ Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with Medito App. If not, see .*/
+import 'dart:ui';
+
import 'package:Medito/network/api_response.dart';
import 'package:Medito/network/downloads/downloads_bloc.dart';
import 'package:Medito/network/session_options/session_options_bloc.dart';
@@ -114,7 +116,8 @@ class _SessionOptionsScreenState extends State {
style: Theme.of(context)
.textTheme
.subtitle2
- .copyWith(color: parseColor(snapshot.data)));
+ .copyWith(
+ color: parseColor(snapshot.data)));
}),
)),
)
@@ -167,6 +170,9 @@ class _SessionOptionsScreenState extends State {
Widget _buildOptionsPanel(List items) {
var childList = [];
+ // To check whether any of the VoiceItems contains a narrator
+ var containsNarrator =
+ items.any((voiceItem) => voiceItem.headerValue.isNotEmptyAndNotNull());
items.forEach((value) {
var section = _getListItem(context, value);
@@ -186,11 +192,13 @@ class _SessionOptionsScreenState extends State {
Container(height: 20),
Padding(
padding: EdgeInsets.only(left: 16),
- child: Text(PICK_NARRATOR.toUpperCase(),
- style: Theme.of(context)
- .textTheme
- .bodyText1
- .copyWith(color: MeditoColors.meditoTextGrey))),
+ child: Text(
+ containsNarrator
+ ? PICK_NARRATOR_AND_DURATION.toUpperCase()
+ : PICK_DURATION.toUpperCase(),
+ style: Theme.of(context).textTheme.bodyText1.copyWith(
+ color: MeditoColors.meditoTextGrey,
+ fontWeight: FontWeight.w600))),
Container(height: 12),
Column(children: childList),
])));
@@ -276,22 +284,21 @@ class _DownloadPanelWidgetState extends State {
borderRadius: BorderRadius.circular(16),
),
child: Padding(
- padding: EdgeInsets.symmetric(vertical: 20, horizontal: 16),
+ padding: EdgeInsets.only(top: 20, bottom: 16, left: 16, right: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(DOWNLOAD.toUpperCase(),
- style: Theme.of(context).textTheme.bodyText2),
+ style: Theme.of(context).textTheme.bodyText1.copyWith(
+ color: MeditoColors.meditoTextGrey,
+ fontWeight: FontWeight.w600)),
Container(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(_getDownloadLabel(),
- style: Theme.of(context)
- .textTheme
- .bodyText1
- .copyWith(color: MeditoColors.meditoTextGrey)),
+ style: Theme.of(context).textTheme.bodyText1),
_getTrailing()
],
)
diff --git a/pubspec.yaml b/pubspec.yaml
index 90ed237d..18504bbd 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,7 +11,7 @@ description: A meditation learning tool
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 2.0.23+20023
+version: 2.0.24+20024
environment:
sdk: ">=2.6.0 <3.0.0"
@@ -27,10 +27,10 @@ dependencies:
shared_preferences: ^2.0.5
cached_network_image: ^3.0.0
auto_size_text: ^3.0.0-nullsafety.0
- just_audio: ^0.7.4
+ just_audio: ^0.9.5
audio_session: ^0.1.0
audio_service: ^0.17.0
- rxdart: ^0.26.0
+ rxdart: ^0.27.1
share: ^2.0.1
firebase_core: ^1.1.0
firebase_messaging: ^10.0.0