From 812fb7a2e38981320136bff601c69536d063212b Mon Sep 17 00:00:00 2001 From: Michael Speed Date: Fri, 22 Mar 2024 16:03:54 +0100 Subject: [PATCH] Start service on when audio starts --- android/app/src/main/AndroidManifest.xml | 4 +- .../medito/AudioPlayerService.kt | 14 +- .../meditofoundation/medito/MainActivity.kt | 34 +-- android/gradle.properties | 3 +- lib/main.dart | 13 +- lib/providers/player/player_provider.dart | 17 +- lib/utils/cache.dart | 101 ------- lib/utils/stats_utils.dart | 282 ------------------ lib/utils/utils.dart | 12 - lib/views/auth/join_welcome_view.dart | 6 +- lib/views/downloads/downloads_view.dart | 1 - .../debug/debug_bottom_sheet_widget.dart | 1 - pigeon_conf.dart | 5 + 13 files changed, 52 insertions(+), 441 deletions(-) delete mode 100644 lib/utils/cache.dart delete mode 100644 lib/utils/stats_utils.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1d38e4ba..cce443e2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ - + @@ -33,9 +33,9 @@ .*/ import 'dart:async'; +import 'dart:io'; import 'package:Medito/constants/constants.dart'; import 'package:Medito/constants/theme/app_theme.dart'; import 'package:Medito/providers/providers.dart'; import 'package:Medito/routes/routes.dart'; import 'package:Medito/src/audio_pigeon.g.dart'; -import 'package:Medito/utils/stats_utils.dart'; import 'package:Medito/utils/utils.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; @@ -33,6 +33,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'constants/environments/environment_constants.dart'; import 'services/notifications/notifications_service.dart'; +final _androidServiceApi = MeditoAndroidAudioServiceManager(); var audioStateNotifier = AudioStateNotifier(); var currentEnvironment = EnvironmentConstants.stagingEnv; @@ -82,16 +83,6 @@ class ParentWidget extends ConsumerStatefulWidget { class _ParentWidgetState extends ConsumerState with WidgetsBindingObserver { - AppLifecycleState currentState = AppLifecycleState.resumed; - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.resumed) { - unawaited(updateStatsFromBg(ref)); - } - currentState = state; - } - @override void dispose() { WidgetsBinding.instance.removeObserver(this); diff --git a/lib/providers/player/player_provider.dart b/lib/providers/player/player_provider.dart index 93d7a3f0..4b19b26f 100644 --- a/lib/providers/player/player_provider.dart +++ b/lib/providers/player/player_provider.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:Medito/models/models.dart'; import 'package:flutter/foundation.dart'; @@ -17,6 +18,7 @@ import '../shared_preference/shared_preference_provider.dart'; import 'download/audio_downloader_provider.dart'; final _api = MeditoAudioServiceApi(); +final _androidServiceApi = MeditoAndroidAudioServiceManager(); final playerProvider = StateNotifierProvider((ref) { @@ -72,6 +74,11 @@ class PlayerProvider extends StateNotifier { var downloadPath = await ref.read(audioDownloaderProvider).getTrackPath( _constructFileName(track, file), ); + if (Platform.isAndroid) { + await _androidServiceApi.startService(); + // wait half a sec for the service to start + await Future.delayed(Duration(milliseconds: 500)); + } await _api.playAudio( AudioData( url: downloadPath ?? file.path, @@ -161,10 +168,12 @@ class PlayerProvider extends StateNotifier { fileGuide: guide, timestamp: DateTime.now().millisecondsSinceEpoch, ); - ref.read(audioStartedEventProvider( - event: audio.toJson(), - trackId: trackId, - )); + ref.read( + audioStartedEventProvider( + event: audio.toJson(), + trackId: trackId, + ), + ); } Future seekToPosition(int position) async { diff --git a/lib/utils/cache.dart b/lib/utils/cache.dart deleted file mode 100644 index 8d576a77..00000000 --- a/lib/utils/cache.dart +++ /dev/null @@ -1,101 +0,0 @@ -/*This file is part of Medito App. - -Medito App is free software: you can redistribute it and/or modify -it under the terms of the Affero GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Medito App is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -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:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:path_provider/path_provider.dart'; - -const TILES_ID = 'tiles'; -const AUDIO_SET = 'audio-set'; -const AUDIO_DATA = 'audio-data'; -const ATTRIBUTIONS = 'attrs'; -const TEXT = 'text'; - -Future clearStorage() async { - if (!kIsWeb) { - final cacheDir = await getApplicationDocumentsDirectory(); - - await cacheDir.list(recursive: true).forEach((element) { - try { - if (element.path.endsWith('txt') || element.path.endsWith('mp3')) { - element.deleteSync(recursive: true); - } - } catch (e) { - print(e); - } - }); - } -} - -Future get _localPath async { - if (kIsWeb) return ''; - final directory = await getApplicationDocumentsDirectory(); - - return directory.path; -} - -Future _localFile(String name) async { - final path = await _localPath; - - return File('$path/$name.txt'); -} - -Future _readCache(String id) async { - id = id.replaceAll('/', '+'); - - final file = await _localFile(id); - - var lastModified; - try { - lastModified = await file.lastModified(); - } on FileSystemException { - return null; - } - - if (lastModified.add(Duration(days: 1)).isBefore(DateTime.now())) return null; - - // Read the file. - return await file.readAsString(); -} - -String? encoded(obj) { - return obj != null ? json.encode(obj) : null; -} - -//ignore:avoid-dynamic -dynamic decoded(String obj) { - return json.decode(obj); -} - -Future writeJSONToCache(String? body, String id) async { - if (body != null) { - id = id.replaceAll('/', '+'); - final file = await _localFile(id); - - return file.writeAsString('$body'); - } - - return null; -} - -Future readJSONFromCache(String url) async { - try { - return await _readCache(url); - } catch (e) { - return null; - } -} diff --git a/lib/utils/stats_utils.dart b/lib/utils/stats_utils.dart deleted file mode 100644 index 91910f9b..00000000 --- a/lib/utils/stats_utils.dart +++ /dev/null @@ -1,282 +0,0 @@ -/*This file is part of Medito App. - -Medito App is free software: you can redistribute it and/or modify -it under the terms of the Affero GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Medito App is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -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 'package:Medito/constants/constants.dart'; -import 'package:Medito/providers/providers.dart'; -import 'package:Medito/utils/cache.dart'; -import 'package:Medito/utils/utils.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:pedantic/pedantic.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -//ignore: prefer-match-file-name -enum UnitType { day, min, tracks } - -String getUnits(UnitType type, int _) { - switch (type) { - case UnitType.day: - return 'd'; - case UnitType.min: - return ''; - case UnitType.tracks: - return ''; - } -} - -Future getCurrentStreak() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.streakCount) ?? 0; - var streakList = await getStreakList(); - - if (streakList.isNotEmpty) { - var lastDayInStreak = - DateTime.fromMillisecondsSinceEpoch(int.parse(streakList.last)); - - final now = DateTime.now(); - - if (longerThanOneDayAgo(lastDayInStreak, now)) { - streak = 0; - await prefs.setInt(SharedPreferenceConstants.streakCount, streak); - } - } - - return streak.toString(); -} - -Future> getStreakList() async { - var prefs = await SharedPreferences.getInstance(); - - return prefs.getStringList(SharedPreferenceConstants.streakList) ?? []; -} - -Future setStreakList(List streakList) async { - var prefs = await SharedPreferences.getInstance(); - - return prefs.setStringList(SharedPreferenceConstants.streakList, streakList); -} - -Future _getCurrentStreakInt() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.streakCount); - - return streak ?? 0; -} - -Future updateMinuteCounter(int additionalSecs) async { - var prefs = await SharedPreferences.getInstance(); - - var current = await _getSecondsListened(); - var plusOne = current + additionalSecs; - await prefs.setInt(SharedPreferenceConstants.secsListened, plusOne); - - return true; -} - -Future updateStreak({String streak = ''}) async { - var prefs = await SharedPreferences.getInstance(); - - if (streak.isNotEmpty) { - await prefs.setInt( - SharedPreferenceConstants.streakCount, - int.parse(streak), - ); - await _updateLongestStreak(int.parse(streak), prefs); - await addPhantomTrackToStreakList(); - - return; - } - - var streakList = await getStreakList(); - var streakCount = prefs.getInt(SharedPreferenceConstants.streakCount) ?? 0; - - if (streakList.isNotEmpty) { - //if you have meditated before, was it on today? if not, increase counter - final lastDayInStreak = - DateTime.fromMillisecondsSinceEpoch(int.parse(streakList.last)); - final now = DateTime.now(); - - if (!isSameDay(lastDayInStreak, now)) { - await incrementStreakCounter(streakCount); - } - } else { - //if you've never done one before - await incrementStreakCounter(streakCount); - } - - streakList.add(DateTime.now().millisecondsSinceEpoch.toString()); - await setStreakList(streakList); -} - -Future addPhantomTrackToStreakList() async { - //add this track to the streak list to stop it resetting to 0, - // but keep a note of it in fakeStreakList - - var prefs = await SharedPreferences.getInstance(); - var streakList = await getStreakList(); - var fakeStreakList = - prefs.getStringList(SharedPreferenceConstants.fakeStreakList) ?? []; - var streakTime = DateTime.now().millisecondsSinceEpoch.toString(); - streakList.add(streakTime); - fakeStreakList.add(streakTime); - await setStreakList(streakList); - await prefs.setStringList( - SharedPreferenceConstants.fakeStreakList, - fakeStreakList, - ); -} - -Future incrementStreakCounter( - int streakCount, -) async { - var prefs = await SharedPreferences.getInstance(); - - streakCount++; - await prefs.setInt(SharedPreferenceConstants.streakCount, streakCount); - - //update longestStreak - await _updateLongestStreak(streakCount, prefs); -} - -Future _updateLongestStreak(int streakCount, SharedPreferences prefs) async { - //update longestStreak - var longest = await _getLongestStreakInt(); - if (streakCount > longest) { - await prefs.setInt(SharedPreferenceConstants.longestStreak, streakCount); - } -} - -void setLongestStreakToCurrentStreak() async { - var prefs = await SharedPreferences.getInstance(); - var current = await _getCurrentStreakInt(); - await prefs.setInt(SharedPreferenceConstants.longestStreak, current); -} - -Future getMinutesListened() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.secsListened); - - return streak == null ? '0' : Duration(seconds: streak).inMinutes.toString(); -} - -Future _getSecondsListened() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.secsListened); - - return streak ?? 0; -} - -Future getLongestStreak() async { - var prefs = await SharedPreferences.getInstance(); - - if (await _getLongestStreakInt() < await _getCurrentStreakInt()) { - return getCurrentStreak(); - } - - var streak = prefs.getInt(SharedPreferenceConstants.longestStreak); - - return streak == null ? '0' : streak.toString(); -} - -Future _getLongestStreakInt() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.longestStreak); - - return streak ?? 0; -} - -Future getNumTracks() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.numSessions); - - return streak == null ? '0' : streak.toString(); -} - -Future getNumTracksInt() async { - var prefs = await SharedPreferences.getInstance(); - - var streak = prefs.getInt(SharedPreferenceConstants.numSessions); - - return streak ?? 0; -} - -Future incrementNumTracks() async { - var prefs = await SharedPreferences.getInstance(); - var current = await getNumTracksInt(); - current++; - await prefs.setInt(SharedPreferenceConstants.numSessions, current); - - return current; -} - -void markAsListened(WidgetRef ref, String id) { - unawaited(ref - .read(sharedPreferencesProvider) - .setBool(SharedPreferenceConstants.listened + id, true)); -} - -Future clearBgStats() { - return writeJSONToCache('', SharedPreferenceConstants.stats); -} - -void setVersionCopySeen(int id) async { - var prefs = await SharedPreferences.getInstance(); - await prefs.setInt(SharedPreferenceConstants.copy, id); -} - -Future getVersionCopyInt() async { - var prefs = await SharedPreferences.getInstance(); - - var version = prefs.getInt(SharedPreferenceConstants.copy); - - return version ?? -1; -} - -bool isSameDay(DateTime day1, DateTime day2) { - return day1.year == day2.year && - day1.month == day2.month && - day1.day == day2.day; -} - -bool longerThanOneDayAgo(DateTime lastDayInStreak, DateTime now) { - var thirtyTwoHoursAfterTime = DateTime.fromMillisecondsSinceEpoch( - lastDayInStreak.millisecondsSinceEpoch + 115200000, - ); - - return now.isAfter(thirtyTwoHoursAfterTime); -} - -Future updateStatsFromBg(WidgetRef ref) async { - var read = await readJSONFromCache(SharedPreferenceConstants.stats); - - if (read.isNotNullAndNotEmpty()) { - var map = decoded(read!); - var id = map[SharedPreferenceConstants.id]; - var secsListened = map[SharedPreferenceConstants.secsListened]; - - if (map != null && map.isNotEmpty) { - await updateStreak(); - await incrementNumTracks(); - markAsListened(ref, id); - await updateMinuteCounter(Duration(seconds: secsListened).inSeconds); - } - await clearBgStats(); - } -} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 910720ce..4ece6598 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -58,12 +58,6 @@ void createSnackBar( } } -bool isDayBefore(DateTime day1, DateTime day2) { - return day1.year == day2.year && - day1.month == day2.month && - day1.day == day2.day - 1; -} - Future launchURLInBrowser(String url) async { try { final uri = Uri.parse(url); @@ -184,12 +178,6 @@ double getBottomPaddingWithStickyMiniPlayer(BuildContext context) { return totalPadding; } -Future getFilePathForOldAppDownloadedFiles(String mediaItemId) async { - var dir = (await getApplicationSupportDirectory()).path; - - return '$dir/${mediaItemId.replaceAll('/', '_').replaceAll(' ', '_')}.mp3'; -} - int formatIcon(String icon) { if (icon.isEmpty) return 0; diff --git a/lib/views/auth/join_welcome_view.dart b/lib/views/auth/join_welcome_view.dart index 69341c60..ca38c4d4 100644 --- a/lib/views/auth/join_welcome_view.dart +++ b/lib/views/auth/join_welcome_view.dart @@ -41,7 +41,11 @@ class JoinWelcomeView extends ConsumerWidget { ), Padding( padding: const EdgeInsets.only( - top: 24, bottom: 16, left: 16, right: 16), + top: 24, + bottom: 16, + left: 16, + right: 16, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/views/downloads/downloads_view.dart b/lib/views/downloads/downloads_view.dart index ce69ba36..ad4ce524 100644 --- a/lib/views/downloads/downloads_view.dart +++ b/lib/views/downloads/downloads_view.dart @@ -14,7 +14,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../providers/background_sounds/background_sounds_notifier.dart'; -import '../../providers/home/home_provider.dart'; class DownloadsView extends ConsumerStatefulWidget { @override diff --git a/lib/views/home/widgets/bottom_sheet/debug/debug_bottom_sheet_widget.dart b/lib/views/home/widgets/bottom_sheet/debug/debug_bottom_sheet_widget.dart index 27ecded5..d6c3cd9a 100644 --- a/lib/views/home/widgets/bottom_sheet/debug/debug_bottom_sheet_widget.dart +++ b/lib/views/home/widgets/bottom_sheet/debug/debug_bottom_sheet_widget.dart @@ -5,7 +5,6 @@ import 'package:Medito/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../../providers/me/me_provider.dart'; import '../share_btn/share_btn_widget.dart'; class DebugBottomSheetWidget extends ConsumerWidget { diff --git a/pigeon_conf.dart b/pigeon_conf.dart index c1d4e074..963d58ae 100644 --- a/pigeon_conf.dart +++ b/pigeon_conf.dart @@ -26,6 +26,11 @@ class AudioData { Track track; } +@HostApi() +abstract class MeditoAndroidAudioServiceManager { + void startService(); +} + @HostApi() abstract class MeditoAudioServiceApi { bool playAudio(AudioData audioData);