New Folder screen and player screen

This commit is contained in:
Michael Speed 2022-12-17 19:36:04 +01:00
parent 214a674feb
commit 5e83cad40c
33 changed files with 1761 additions and 518 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:Medito/utils/utils.dart';
import 'package:audio_service/audio_service.dart';
@ -68,7 +69,7 @@ class MeditoAudioHandler extends BaseAudioHandler
@override
Future<void> play() async {
try {
if (_currentlyPlayingBGSound.isNotEmptyAndNotNull()) {
if (_currentlyPlayingBGSound.isNotEmptyAndNotNull() && mediaItemHasBGSound()) {
unawaited(_bgPlayer.play());
unawaited(_bgPlayer.setLoopMode(LoopMode.all));
}
@ -203,7 +204,26 @@ class MeditoAudioHandler extends BaseAudioHandler
Future<void> _setBgVolumeFadeAtEnd(int timeLeft) async {
print(_bgPlayer.volume - (_bgVolume / FADE_DURATION));
if (_bgPlayer.volume > 0) {
unawaited(_bgPlayer.setVolume(_bgPlayer.volume - (_bgVolume / FADE_DURATION)));
unawaited(
_bgPlayer.setVolume(_bgPlayer.volume - (_bgVolume / FADE_DURATION)));
}
}
void skipForward30Secs() {
var seekDuration = min(_duration?.inMilliseconds ?? 0,
_player.position.inMilliseconds + Duration(seconds: 30).inMilliseconds);
_player.seek(Duration(milliseconds: seekDuration));
}
void skipBackward10Secs() {
var seekDuration = max(0,
_player.position.inMilliseconds - Duration(seconds: 10).inMilliseconds);
_player.seek(Duration(milliseconds: seekDuration));
}
void setPlayerSpeed(double speed) => _player.setSpeed(speed);
bool mediaItemHasBGSound() => mediaItem.value?.extras?[HAS_BG_SOUND];
}

View File

@ -24,12 +24,12 @@ import 'package:audio_service/audio_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'audioplayer/audio_inherited_widget.dart';
import 'network/auth.dart';
import 'utils/colors.dart';
late SharedPreferences sharedPreferences;
@ -62,7 +62,12 @@ Future<void> main() async {
}
}
void _runApp(MeditoAudioHandler _audioHandler) => runApp(AudioHandlerInheritedWidget(audioHandler: _audioHandler, child: ParentWidget()));
void _runApp(MeditoAudioHandler _audioHandler) => runApp(ProviderScope(
child: AudioHandlerInheritedWidget(
audioHandler: _audioHandler,
child: ParentWidget(),
),
));
/// This Widget is the main application widget.
class ParentWidget extends StatefulWidget {

View File

@ -26,6 +26,7 @@ class FolderItemsRepository {
try {
final response = await httpGet(BASE_URL + ext + id + folderParameters,
fileNameForCache: BASE_URL + id + '/' + ext, skipCache: skipCache);
if(response == null) return null;
return FolderResponse.fromJson(response);
} catch (e) {
print(e);

View File

@ -0,0 +1,41 @@
import 'package:Medito/network/http_get.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../auth.dart';
import 'new_folder_response.dart';
var folderParameters =
'?fields=*,items.item:folders.id,items.item:folders.type,items.item:folders.title,items.item:folders.subtitle,items.item:sessions.id,items.item:sessions.type,items.item:sessions.title,items.item:sessions.subtitle,items.item:sessions.old_id,items.item:dailies.id,items.item:dailies.type,items.item:dailies.title,items.item:dailies.subtitle,items.item:articles.id,items.item:articles.type,items.item:articles.title,items.item:articles.subtitle&deep[items][_sort]=position';
var ext = 'items/folders/';
final folderProvider =
FutureProvider.family<NewFolderResponse?, String?>((ref, id) async {
if (id == null) {
throw Exception('Id is null');
}
final skipCache = ref.watch(folderSkipCacheProvider);
final content = await _httpGet(id, skipCache);
if(content == null) return null;
return NewFolderResponse.fromJson(content);
});
final folderProviderSkipCache =
FutureProvider.family<NewFolderResponse?, String>((ref, id) async {
final content = await _httpGet(id, true);
if(content == null) return null;
return NewFolderResponse.fromJson(content);
});
Future<Map<String,Object?>?> _httpGet(String id, bool skipCache) {
return httpGet(
BASE_URL + ext + id + folderParameters,
fileNameForCache: BASE_URL + id + '/' + ext,
skipCache: skipCache,
);
}
final folderSkipCacheProvider = StateProvider<bool>((ref) => false);

View File

@ -0,0 +1,45 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'new_folder_response.freezed.dart';
part 'new_folder_response.g.dart';
@freezed
class NewFolderResponse with _$NewFolderResponse {
const factory NewFolderResponse({
required Data? data,
}) = _NewFolderResponse;
factory NewFolderResponse.fromJson(Map<String, Object?> json) =>
_$NewFolderResponseFromJson(json);
}
@freezed
class Data with _$Data {
const factory Data(
{int? id,
String? title,
String? subtitle,
String? description,
List<FolderItem>? items}) = _Data;
factory Data.fromJson(Map<String, Object?> json) => _$DataFromJson(json);
}
@freezed
class FolderItem with _$FolderItem {
const factory FolderItem({
Item? item,
}) = _FolderItem;
factory FolderItem.fromJson(Map<String, Object?> json) =>
_$FolderItemFromJson(json);
}
@freezed
class Item with _$Item {
const factory Item({int? id, String? type, String? title, String? subtitle}) =
_Item;
factory Item.fromJson(Map<String, Object?> json) => _$ItemFromJson(json);
}

View File

@ -0,0 +1,765 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'new_folder_response.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
NewFolderResponse _$NewFolderResponseFromJson(Map<String, dynamic> json) {
return _NewFolderResponse.fromJson(json);
}
/// @nodoc
mixin _$NewFolderResponse {
Data? get data => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$NewFolderResponseCopyWith<NewFolderResponse> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $NewFolderResponseCopyWith<$Res> {
factory $NewFolderResponseCopyWith(
NewFolderResponse value, $Res Function(NewFolderResponse) then) =
_$NewFolderResponseCopyWithImpl<$Res, NewFolderResponse>;
@useResult
$Res call({Data? data});
$DataCopyWith<$Res>? get data;
}
/// @nodoc
class _$NewFolderResponseCopyWithImpl<$Res, $Val extends NewFolderResponse>
implements $NewFolderResponseCopyWith<$Res> {
_$NewFolderResponseCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? data = freezed,
}) {
return _then(_value.copyWith(
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as Data?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$DataCopyWith<$Res>? get data {
if (_value.data == null) {
return null;
}
return $DataCopyWith<$Res>(_value.data!, (value) {
return _then(_value.copyWith(data: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$_NewFolderResponseCopyWith<$Res>
implements $NewFolderResponseCopyWith<$Res> {
factory _$$_NewFolderResponseCopyWith(_$_NewFolderResponse value,
$Res Function(_$_NewFolderResponse) then) =
__$$_NewFolderResponseCopyWithImpl<$Res>;
@override
@useResult
$Res call({Data? data});
@override
$DataCopyWith<$Res>? get data;
}
/// @nodoc
class __$$_NewFolderResponseCopyWithImpl<$Res>
extends _$NewFolderResponseCopyWithImpl<$Res, _$_NewFolderResponse>
implements _$$_NewFolderResponseCopyWith<$Res> {
__$$_NewFolderResponseCopyWithImpl(
_$_NewFolderResponse _value, $Res Function(_$_NewFolderResponse) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? data = freezed,
}) {
return _then(_$_NewFolderResponse(
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as Data?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_NewFolderResponse
with DiagnosticableTreeMixin
implements _NewFolderResponse {
const _$_NewFolderResponse({required this.data});
factory _$_NewFolderResponse.fromJson(Map<String, dynamic> json) =>
_$$_NewFolderResponseFromJson(json);
@override
final Data? data;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'NewFolderResponse(data: $data)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'NewFolderResponse'))
..add(DiagnosticsProperty('data', data));
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_NewFolderResponse &&
(identical(other.data, data) || other.data == data));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, data);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_NewFolderResponseCopyWith<_$_NewFolderResponse> get copyWith =>
__$$_NewFolderResponseCopyWithImpl<_$_NewFolderResponse>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_NewFolderResponseToJson(
this,
);
}
}
abstract class _NewFolderResponse implements NewFolderResponse {
const factory _NewFolderResponse({required final Data? data}) =
_$_NewFolderResponse;
factory _NewFolderResponse.fromJson(Map<String, dynamic> json) =
_$_NewFolderResponse.fromJson;
@override
Data? get data;
@override
@JsonKey(ignore: true)
_$$_NewFolderResponseCopyWith<_$_NewFolderResponse> get copyWith =>
throw _privateConstructorUsedError;
}
Data _$DataFromJson(Map<String, dynamic> json) {
return _Data.fromJson(json);
}
/// @nodoc
mixin _$Data {
int? get id => throw _privateConstructorUsedError;
String? get title => throw _privateConstructorUsedError;
String? get subtitle => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError;
List<FolderItem>? get items => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DataCopyWith<Data> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DataCopyWith<$Res> {
factory $DataCopyWith(Data value, $Res Function(Data) then) =
_$DataCopyWithImpl<$Res, Data>;
@useResult
$Res call(
{int? id,
String? title,
String? subtitle,
String? description,
List<FolderItem>? items});
}
/// @nodoc
class _$DataCopyWithImpl<$Res, $Val extends Data>
implements $DataCopyWith<$Res> {
_$DataCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? title = freezed,
Object? subtitle = freezed,
Object? description = freezed,
Object? items = freezed,
}) {
return _then(_value.copyWith(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
subtitle: freezed == subtitle
? _value.subtitle
: subtitle // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
items: freezed == items
? _value.items
: items // ignore: cast_nullable_to_non_nullable
as List<FolderItem>?,
) as $Val);
}
}
/// @nodoc
abstract class _$$_DataCopyWith<$Res> implements $DataCopyWith<$Res> {
factory _$$_DataCopyWith(_$_Data value, $Res Function(_$_Data) then) =
__$$_DataCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int? id,
String? title,
String? subtitle,
String? description,
List<FolderItem>? items});
}
/// @nodoc
class __$$_DataCopyWithImpl<$Res> extends _$DataCopyWithImpl<$Res, _$_Data>
implements _$$_DataCopyWith<$Res> {
__$$_DataCopyWithImpl(_$_Data _value, $Res Function(_$_Data) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? title = freezed,
Object? subtitle = freezed,
Object? description = freezed,
Object? items = freezed,
}) {
return _then(_$_Data(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
subtitle: freezed == subtitle
? _value.subtitle
: subtitle // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
items: freezed == items
? _value._items
: items // ignore: cast_nullable_to_non_nullable
as List<FolderItem>?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_Data with DiagnosticableTreeMixin implements _Data {
const _$_Data(
{this.id,
this.title,
this.subtitle,
this.description,
final List<FolderItem>? items})
: _items = items;
factory _$_Data.fromJson(Map<String, dynamic> json) => _$$_DataFromJson(json);
@override
final int? id;
@override
final String? title;
@override
final String? subtitle;
@override
final String? description;
final List<FolderItem>? _items;
@override
List<FolderItem>? get items {
final value = _items;
if (value == null) return null;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Data(id: $id, title: $title, subtitle: $subtitle, description: $description, items: $items)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Data'))
..add(DiagnosticsProperty('id', id))
..add(DiagnosticsProperty('title', title))
..add(DiagnosticsProperty('subtitle', subtitle))
..add(DiagnosticsProperty('description', description))
..add(DiagnosticsProperty('items', items));
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_Data &&
(identical(other.id, id) || other.id == id) &&
(identical(other.title, title) || other.title == title) &&
(identical(other.subtitle, subtitle) ||
other.subtitle == subtitle) &&
(identical(other.description, description) ||
other.description == description) &&
const DeepCollectionEquality().equals(other._items, _items));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id, title, subtitle, description,
const DeepCollectionEquality().hash(_items));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_DataCopyWith<_$_Data> get copyWith =>
__$$_DataCopyWithImpl<_$_Data>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_DataToJson(
this,
);
}
}
abstract class _Data implements Data {
const factory _Data(
{final int? id,
final String? title,
final String? subtitle,
final String? description,
final List<FolderItem>? items}) = _$_Data;
factory _Data.fromJson(Map<String, dynamic> json) = _$_Data.fromJson;
@override
int? get id;
@override
String? get title;
@override
String? get subtitle;
@override
String? get description;
@override
List<FolderItem>? get items;
@override
@JsonKey(ignore: true)
_$$_DataCopyWith<_$_Data> get copyWith => throw _privateConstructorUsedError;
}
FolderItem _$FolderItemFromJson(Map<String, dynamic> json) {
return _FolderItem.fromJson(json);
}
/// @nodoc
mixin _$FolderItem {
Item? get item => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FolderItemCopyWith<FolderItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FolderItemCopyWith<$Res> {
factory $FolderItemCopyWith(
FolderItem value, $Res Function(FolderItem) then) =
_$FolderItemCopyWithImpl<$Res, FolderItem>;
@useResult
$Res call({Item? item});
$ItemCopyWith<$Res>? get item;
}
/// @nodoc
class _$FolderItemCopyWithImpl<$Res, $Val extends FolderItem>
implements $FolderItemCopyWith<$Res> {
_$FolderItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? item = freezed,
}) {
return _then(_value.copyWith(
item: freezed == item
? _value.item
: item // ignore: cast_nullable_to_non_nullable
as Item?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$ItemCopyWith<$Res>? get item {
if (_value.item == null) {
return null;
}
return $ItemCopyWith<$Res>(_value.item!, (value) {
return _then(_value.copyWith(item: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$_FolderItemCopyWith<$Res>
implements $FolderItemCopyWith<$Res> {
factory _$$_FolderItemCopyWith(
_$_FolderItem value, $Res Function(_$_FolderItem) then) =
__$$_FolderItemCopyWithImpl<$Res>;
@override
@useResult
$Res call({Item? item});
@override
$ItemCopyWith<$Res>? get item;
}
/// @nodoc
class __$$_FolderItemCopyWithImpl<$Res>
extends _$FolderItemCopyWithImpl<$Res, _$_FolderItem>
implements _$$_FolderItemCopyWith<$Res> {
__$$_FolderItemCopyWithImpl(
_$_FolderItem _value, $Res Function(_$_FolderItem) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? item = freezed,
}) {
return _then(_$_FolderItem(
item: freezed == item
? _value.item
: item // ignore: cast_nullable_to_non_nullable
as Item?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_FolderItem with DiagnosticableTreeMixin implements _FolderItem {
const _$_FolderItem({this.item});
factory _$_FolderItem.fromJson(Map<String, dynamic> json) =>
_$$_FolderItemFromJson(json);
@override
final Item? item;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'FolderItem(item: $item)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'FolderItem'))
..add(DiagnosticsProperty('item', item));
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_FolderItem &&
(identical(other.item, item) || other.item == item));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, item);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_FolderItemCopyWith<_$_FolderItem> get copyWith =>
__$$_FolderItemCopyWithImpl<_$_FolderItem>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_FolderItemToJson(
this,
);
}
}
abstract class _FolderItem implements FolderItem {
const factory _FolderItem({final Item? item}) = _$_FolderItem;
factory _FolderItem.fromJson(Map<String, dynamic> json) =
_$_FolderItem.fromJson;
@override
Item? get item;
@override
@JsonKey(ignore: true)
_$$_FolderItemCopyWith<_$_FolderItem> get copyWith =>
throw _privateConstructorUsedError;
}
Item _$ItemFromJson(Map<String, dynamic> json) {
return _Item.fromJson(json);
}
/// @nodoc
mixin _$Item {
int? get id => throw _privateConstructorUsedError;
String? get type => throw _privateConstructorUsedError;
String? get title => throw _privateConstructorUsedError;
String? get subtitle => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ItemCopyWith<Item> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ItemCopyWith<$Res> {
factory $ItemCopyWith(Item value, $Res Function(Item) then) =
_$ItemCopyWithImpl<$Res, Item>;
@useResult
$Res call({int? id, String? type, String? title, String? subtitle});
}
/// @nodoc
class _$ItemCopyWithImpl<$Res, $Val extends Item>
implements $ItemCopyWith<$Res> {
_$ItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? type = freezed,
Object? title = freezed,
Object? subtitle = freezed,
}) {
return _then(_value.copyWith(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
type: freezed == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String?,
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
subtitle: freezed == subtitle
? _value.subtitle
: subtitle // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$_ItemCopyWith<$Res> implements $ItemCopyWith<$Res> {
factory _$$_ItemCopyWith(_$_Item value, $Res Function(_$_Item) then) =
__$$_ItemCopyWithImpl<$Res>;
@override
@useResult
$Res call({int? id, String? type, String? title, String? subtitle});
}
/// @nodoc
class __$$_ItemCopyWithImpl<$Res> extends _$ItemCopyWithImpl<$Res, _$_Item>
implements _$$_ItemCopyWith<$Res> {
__$$_ItemCopyWithImpl(_$_Item _value, $Res Function(_$_Item) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? type = freezed,
Object? title = freezed,
Object? subtitle = freezed,
}) {
return _then(_$_Item(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
type: freezed == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String?,
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
subtitle: freezed == subtitle
? _value.subtitle
: subtitle // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_Item with DiagnosticableTreeMixin implements _Item {
const _$_Item({this.id, this.type, this.title, this.subtitle});
factory _$_Item.fromJson(Map<String, dynamic> json) => _$$_ItemFromJson(json);
@override
final int? id;
@override
final String? type;
@override
final String? title;
@override
final String? subtitle;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Item(id: $id, type: $type, title: $title, subtitle: $subtitle)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Item'))
..add(DiagnosticsProperty('id', id))
..add(DiagnosticsProperty('type', type))
..add(DiagnosticsProperty('title', title))
..add(DiagnosticsProperty('subtitle', subtitle));
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_Item &&
(identical(other.id, id) || other.id == id) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.title, title) || other.title == title) &&
(identical(other.subtitle, subtitle) ||
other.subtitle == subtitle));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id, type, title, subtitle);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_ItemCopyWith<_$_Item> get copyWith =>
__$$_ItemCopyWithImpl<_$_Item>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_ItemToJson(
this,
);
}
}
abstract class _Item implements Item {
const factory _Item(
{final int? id,
final String? type,
final String? title,
final String? subtitle}) = _$_Item;
factory _Item.fromJson(Map<String, dynamic> json) = _$_Item.fromJson;
@override
int? get id;
@override
String? get type;
@override
String? get title;
@override
String? get subtitle;
@override
@JsonKey(ignore: true)
_$$_ItemCopyWith<_$_Item> get copyWith => throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,64 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'new_folder_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_NewFolderResponse _$$_NewFolderResponseFromJson(Map<String, dynamic> json) =>
_$_NewFolderResponse(
data: json['data'] == null
? null
: Data.fromJson(json['data'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$_NewFolderResponseToJson(
_$_NewFolderResponse instance) =>
<String, dynamic>{
'data': instance.data,
};
_$_Data _$$_DataFromJson(Map<String, dynamic> json) => _$_Data(
id: json['id'] as int?,
title: json['title'] as String?,
subtitle: json['subtitle'] as String?,
description: json['description'] as String?,
items: (json['items'] as List<dynamic>?)
?.map((e) => FolderItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$_DataToJson(_$_Data instance) => <String, dynamic>{
'id': instance.id,
'title': instance.title,
'subtitle': instance.subtitle,
'description': instance.description,
'items': instance.items,
};
_$_FolderItem _$$_FolderItemFromJson(Map<String, dynamic> json) =>
_$_FolderItem(
item: json['item'] == null
? null
: Item.fromJson(json['item'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$_FolderItemToJson(_$_FolderItem instance) =>
<String, dynamic>{
'item': instance.item,
};
_$_Item _$$_ItemFromJson(Map<String, dynamic> json) => _$_Item(
id: json['id'] as int?,
type: json['type'] as String?,
title: json['title'] as String?,
subtitle: json['subtitle'] as String?,
);
Map<String, dynamic> _$$_ItemToJson(_$_Item instance) => <String, dynamic>{
'id': instance.id,
'type': instance.type,
'title': instance.title,
'subtitle': instance.subtitle,
};

View File

@ -0,0 +1,205 @@
import 'package:Medito/network/folder/new_folder_response.dart';
import 'package:Medito/utils/navigation_extra.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../utils/strings.dart';
import '../../utils/utils.dart';
import 'folder_provider.dart';
class NewFolderScreen extends ConsumerWidget {
const NewFolderScreen({Key? key, required this.id}) : super(key: key);
final String? id;
@override
Widget build(BuildContext context, WidgetRef ref) {
var value = ref.watch(folderProvider(id));
return value.when(
data: (data) => buildScaffoldWithData(data, ref),
error: (err, stack) => Text(err.toString()),
loading: () => _buildLoadingWidget());
}
Widget _buildLoadingWidget() =>
const Center(child: CircularProgressIndicator());
Scaffold buildScaffoldWithData(NewFolderResponse? folder, WidgetRef ref) {
return Scaffold(
body: RefreshIndicator(
onRefresh: () async {
ref.read(folderSkipCacheProvider.notifier).state = true;
},
child: ListView(
children: [
HeaderWidget(folder?.data?.title, folder?.data?.description, ''),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: folder?.data?.items?.length ?? 0,
shrinkWrap: true,
itemBuilder: (BuildContext context, int i) {
var title = folder?.data?.items?[i].item?.title;
var subtitle = folder?.data?.items?[i].item?.subtitle;
return GestureDetector(
onTap: () => _onListItemTap(folder?.data?.items?[i].item?.id,
folder?.data?.items?[i].item?.type, ref.context),
child: _buildListTile(title, subtitle, true),
);
},
)
],
),
),
);
}
Widget _buildListTile(String? title, String? subtitle, bool showIcon) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null) Text(title),
if (subtitle != null)
Column(children: [
Container(height: 8),
Text(subtitle),
])
],
),
showIcon ? Icon(_getIcon(), color: Colors.white) : Container()
],
),
);
}
void _onListItemTap(int? id, String? type, BuildContext context) {
checkConnectivity().then((value) {
if (value) {
var location = GoRouter.of(context).location;
if (type == 'folder') {
if (location.contains('folder2')) {
context.go(getPathFromString(Folder3Path,
[location.split('/')[2], this.id, id.toString()]));
} else {
context.go(
getPathFromString(Folder2Path, [this.id, id.toString()]));
}
} else {
context.go(location + getPathFromString(type, [id.toString()]));
}
} else {
createSnackBar(CHECK_CONNECTION, context);
}
});
}
IconData _getIcon() {
return Icons.check_circle_outline_sharp;
// return Icons.article_outlined;
// return Icons.arrow_forward_ios_sharp;
}
}
class HeaderWidget extends StatelessWidget {
const HeaderWidget(this.title, this.description, this.imageUrl, {Key? key})
: super(key: key);
final title;
final description;
final imageUrl;
@override
Widget build(BuildContext context) {
return Column(
children: [
AspectRatio(
aspectRatio: 1.3,
child: Stack(
children: [
buildHeroImage(imageUrl),
_buildMeditoRoundCloseButton(),
_buildTitleText(context),
],
),
),
if (description != null && description != '') _buildDescription(context)
],
);
}
Widget _buildDescription(BuildContext context) {
return Container(
color: Colors.grey.shade900,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
description ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: Colors.white),
),
),
);
}
Widget _buildMeditoRoundCloseButton() {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: MeditoRoundCloseButton(),
),
);
}
Widget _buildTitleText(BuildContext context) {
return Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.only(
left: 20.0,
right: 20.0,
bottom: 12.0,
),
child: Text(
title,
style: Theme.of(context).primaryTextTheme.headline4,
),
),
);
}
Image buildHeroImage(String imageUrl) {
return Image.asset(
'assets/images/dalle.png',
fit: BoxFit.fill,
);
}
}
class MeditoRoundCloseButton extends StatelessWidget {
const MeditoRoundCloseButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () {
router.pop();
},
color: Colors.black38,
padding: EdgeInsets.all(16),
shape: CircleBorder(),
child: Icon(
Icons.close,
color: Colors.white,
),
);
}
}

View File

@ -20,8 +20,9 @@ import 'package:Medito/network/http_get.dart';
class CoursesRepo {
final _ext = 'items/courses';
Future<CoursesResponse> fetchCourses([bool skipCache = false]) async {
Future<CoursesResponse?> fetchCourses([bool skipCache = false]) async {
final response = await httpGet(BASE_URL + _ext, skipCache: skipCache);
if(response == null) return null;
return CoursesResponse.fromJson(response);
}
}

View File

@ -20,8 +20,9 @@ import 'package:Medito/network/http_get.dart';
class DailyMessageRepo {
final _ext = 'items/daily_message';
Future<DailyMessageResponse>? getMessage([bool skipCache = false]) async {
Future<DailyMessageResponse?>? getMessage([bool skipCache = false]) async {
final response = await httpGet(BASE_URL + _ext, skipCache: skipCache);
if(response == null) return null;
return DailyMessageResponse.fromJson(response);
}
}

View File

@ -13,15 +13,16 @@ 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 <https://www.gnu.org/licenses/>.*/
import 'package:Medito/network/home/menu_response.dart';
import 'package:Medito/network/auth.dart';
import 'package:Medito/network/home/menu_response.dart';
import 'package:Medito/network/http_get.dart';
class HomeRepo {
final _ext = 'items/menu';
Future<MenuResponse> fetchMenu(bool skipCache) async {
Future<MenuResponse?> fetchMenu(bool skipCache) async {
final response = await httpGet(BASE_URL + _ext, skipCache: skipCache);
if (response == null) return null;
return MenuResponse.fromJson(response);
}
}

View File

@ -13,15 +13,16 @@ 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 <https://www.gnu.org/licenses/>.*/
import 'package:Medito/network/home/shortcuts_response.dart';
import 'package:Medito/network/auth.dart';
import 'package:Medito/network/home/shortcuts_response.dart';
import 'package:Medito/network/http_get.dart';
class ShortcutsRepo {
final _ext = 'items/shortcuts';
Future<ShortcutsResponse> fetchShortcuts({bool skipCache = false}) async {
Future<ShortcutsResponse?> fetchShortcuts({bool skipCache = false}) async {
final response = await httpGet(BASE_URL + _ext, skipCache: skipCache);
if (response == null) return null;
return ShortcutsResponse.fromJson(response);
}
}

View File

@ -24,7 +24,7 @@ import 'package:pedantic/pedantic.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
//move this to network package later
Future httpGet(String url,
Future<Map<String, dynamic>?> httpGet(String url,
{bool skipCache = false, String? fileNameForCache}) async {
var cache;

View File

@ -34,6 +34,7 @@ class AnnouncementBloc {
try {
var data = await _repo.fetchAnnouncements(
skipCache: skipCache, hasOpened: hasOpened);
if (data == null) return;
announcementController.add(data);
} catch (e) {
print(e);

View File

@ -21,10 +21,11 @@ class AnnouncementRepository {
var ext = 'items/announcement';
var welcome_ext = 'items/welcome_announcement';
Future<AnnouncementResponse> fetchAnnouncements(
Future<AnnouncementResponse?> fetchAnnouncements(
{bool skipCache = false, bool hasOpened = false}) async {
final response =
await httpGet(BASE_URL + getExt(hasOpened), skipCache: skipCache);
if (response == null) return null;
return AnnouncementResponse.fromJson(response);
}

View File

@ -13,14 +13,17 @@ 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 <https://www.gnu.org/licenses/>.*/
import 'package:Medito/network/packs/packs_response.dart';
import 'package:Medito/network/auth.dart';
import 'package:Medito/network/http_get.dart';
import 'package:Medito/network/packs/packs_response.dart';
class PacksRepository {
Future<List<PacksData>?> fetchPacks(bool skipCache) async {
final response = await httpGet(BASE_URL + 'items/packs', skipCache: skipCache);
final response =
await httpGet(BASE_URL + 'items/packs', skipCache: skipCache);
if (response == null) return null;
return PacksResponse.fromJson(response).data;
}
}

View File

@ -26,6 +26,7 @@ import 'package:audio_service/audio_service.dart';
class PlayerBloc {
PlayerRepository? _repo;
PlayerCopyData? version;
@Deprecated('Use backgroundSoundsProvider instead')
StreamController<ApiResponse<BackgroundSoundsResponse>>?
bgSoundsListController;
@ -44,6 +45,7 @@ class PlayerBloc {
setVersionCopySeen(version?.id ?? 0);
}
@Deprecated('Use backgroundSoundsProvider instead')
Future<void> fetchBackgroundSounds() async {
try {
var sounds = await _repo?.fetchBackgroundSounds(true);
@ -70,3 +72,4 @@ class PlayerBloc {
bgSoundsListController?.close();
}
}

View File

@ -19,22 +19,41 @@ import 'package:Medito/network/http_get.dart';
import 'package:Medito/network/player/audio_complete_copy_response.dart';
import 'package:Medito/network/session_options/background_sounds.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pedantic/pedantic.dart';
import '../user/user_utils.dart';
var ext = 'items/player_copy?fields=*.*';
var bgSoundsUrl = '${BASE_URL}items/background_sounds';
final backgroundSoundsProvider =
FutureProvider<BackgroundSoundsResponse?>((ref) async {
final skipCache = ref.watch(bgSoundsSkipCacheProvider);
final content = await httpGet(bgSoundsUrl, skipCache: skipCache);
if (content == null) return null;
return BackgroundSoundsResponse.fromJson(content);
});
final bgSoundsSkipCacheProvider = StateProvider<bool>((ref) => false);
class PlayerRepository {
final _ext = 'items/player_copy?fields=*.*';
final _ratingExt = 'items/rating';
var bgSoundsUrl = '${BASE_URL}items/background_sounds';
Future<PlayerCopyResponse> fetchCopyData() async {
@Deprecated('Do not use')
Future<PlayerCopyResponse?> fetchCopyData() async {
final response = await httpGet(BASE_URL + _ext);
if (response == null) return null;
return PlayerCopyResponse.fromJson(response);
}
Future<BackgroundSoundsResponse> fetchBackgroundSounds(bool skipCache) async {
@Deprecated('Use backgroundSoundsProvider instead')
Future<BackgroundSoundsResponse?> fetchBackgroundSounds(
bool skipCache) async {
final response = await httpGet(bgSoundsUrl, skipCache: skipCache);
if (response == null) return null;
return BackgroundSoundsResponse.fromJson(response);
}

View File

@ -129,8 +129,8 @@ class SessionOptionsBloc {
Tracking.postUsage(
Tracking.AUDIO_STARTED,
{
Tracking.SESSION_ID: mediaItem.extras?[SESSION_ID],
Tracking.SESSION_DURATION: mediaItem.extras?[LENGTH],
Tracking.SESSION_ID: mediaItem.extras?[SESSION_ID].toString() ?? '',
Tracking.SESSION_DURATION: mediaItem.extras?[LENGTH].toString() ?? '',
Tracking.SESSION_GUIDE: mediaItem.artist ?? ''
},
),

View File

@ -39,6 +39,8 @@ class SessionOptionsRepository {
final response = await httpGet(url, skipCache: skipCache);
if (response == null) return null;
return SessionOptionsResponse.fromJson(response).data;
}

View File

@ -27,10 +27,10 @@ class TextBloc {
var data = await _repo.fetchData(id, skipCache);
if (!titleController.isClosed) {
titleController.sink.add(data.title ?? '');
titleController.sink.add(data?.title ?? '');
}
if (!bodyController.isClosed) {
bodyController.sink.add(data.body ?? '');
bodyController.sink.add(data?.body ?? '');
}
}
}

View File

@ -20,8 +20,9 @@ import 'package:Medito/network/http_get.dart';
class TextRepository {
var ext = 'items/articles/';
Future<TextResponse> fetchData(String id, bool skipCache) async {
Future<TextResponse?> fetchData(String id, bool skipCache) async {
final response = await httpGet(BASE_URL + ext + id, skipCache: skipCache);
if(response == null) return null;
return TextResponse.fromJson(response);
}
}

View File

@ -13,11 +13,25 @@ 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 <https://www.gnu.org/licenses/>.*/
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
const DEFAULT_VOLUME = 0.65;
@Deprecated('Use volume provider instead')
Future<dynamic> retrieveSavedBgVolume() async {
return await _getSavedVolume();
}
final volumeProvider = StateProvider((_) => Volume());
class Volume {
Future<double> getVolume() async {
return await _getSavedVolume();
}
}
Future<double> _getSavedVolume() async {
var prefs = await SharedPreferences.getInstance();
var vol;
try {

View File

@ -21,6 +21,9 @@ class MeditoColors {
static var darkBGColor = Color(0xff22282D);
static var almostBlack = Color(0xEE000000);
static var greyIsTheNewBlack = Color(0xFF1C1C1E);
static var greyIsTheNewGrey = Color(0xFF2C2C2E);
static const newGrey = Color(0xffb6b6b8);
static const midnight = Color(0xff111213);

View File

@ -1,3 +1,4 @@
import 'package:Medito/network/folder/new_folder_screen.dart';
import 'package:Medito/utils/utils.dart';
import 'package:Medito/widgets/btm_nav/downloads_widget.dart';
import 'package:Medito/widgets/session_options/session_options_screen.dart';
@ -5,9 +6,8 @@ import 'package:Medito/widgets/text/text_file_widget.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../widgets/folders/folder_nav_widget.dart';
import '../widgets/home/home_wrapper_widget.dart';
import '../widgets/player/player2/player_widget_2.dart';
import '../widgets/player/player2/new_player_widget.dart';
const String SessionPath = '/session/:sid';
const String DailyPath = '/daily/:did';
@ -101,7 +101,9 @@ GoRoute _getSessionRoute() {
GoRoute _getPlayerRoute() {
return GoRoute(
path: 'player',
pageBuilder: (context, state) => getPlayerMaterialPage(state),
pageBuilder: (context, state) {
return getPlayerMaterialPage(state);
},
);
}
@ -146,48 +148,50 @@ MaterialPage<void> getCollectionMaterialPage(GoRouterState state) {
}
MaterialPage<void> getPlayerMaterialPage(GoRouterState state) {
return MaterialPage(key: state.pageKey, child: PlayerWidget());
return MaterialPage(key: state.pageKey, child: NewPlayerWidget());
}
MaterialPage<void> getFolderMaterialPage(GoRouterState state) {
if (state.params.length == 1) {
return MaterialPage(
key: state.pageKey,
child: FolderNavWidget(id: state.params['fid'] ?? ''));
child: NewFolderScreen(id: state.params['fid']));
} else if (state.params.length == 2) {
return MaterialPage(
key: state.pageKey,
child: FolderNavWidget(id: state.params['f2id'] ?? ''));
child: NewFolderScreen(id: state.params['f2id']));
} else {
return MaterialPage(
key: state.pageKey,
child: FolderNavWidget(id: state.params['f3id'] ?? ''));
child: NewFolderScreen(id: state.params['f3id']));
}
}
String getPathFromString(String? place, List<String> ids) {
String getPathFromString(String? place, List<String?> ids) {
ids.removeWhere((element) => element == null);
if (place == 'session') {
return SessionPath.replaceAll(':sid', ids.first);
return SessionPath.replaceAll(':sid', ids.first!);
}
if (place == 'daily') {
return DailyPath.replaceAll(':did', ids.first);
return DailyPath.replaceAll(':did', ids.first!);
}
if (place == 'donation') {
return DonationPath;
}
if (place == 'article') {
return ArticlePath.replaceAll(':aid', ids.first);
return ArticlePath.replaceAll(':aid', ids.first!);
}
if (place != null && place.contains('folder3')) {
return Folder3Path.replaceAll(':fid', ids.first).replaceAll(':f2id', ids[1])
.replaceAll(':f3id', ids[2]);
return Folder3Path.replaceAll(':fid', ids.first!).replaceAll(':f2id', ids[1]!)
.replaceAll(':f3id', ids[2]!);
}
if (place != null && place.contains('folder2')) {
return Folder2Path.replaceAll(':fid', ids.first)
.replaceAll(':f2id', ids[1]);
return Folder2Path.replaceAll(':fid', ids.first!)
.replaceAll(':f2id', ids[1]!);
}
if (place == 'folder') {
return FolderPath.replaceAll(':fid', ids.first);
return FolderPath.replaceAll(':fid', ids.first!);
}
if (place == 'url') {
launchUrl(ids.first);

View File

@ -21,7 +21,6 @@ class _HomeWrapperWidgetState extends State<HomeWrapperWidget> {
@override
void initState() {
firstOpenOperations().then((hasOpened) {
_hasOpened = hasOpened;
setState(() {
@ -62,27 +61,27 @@ class _HomeWrapperWidgetState extends State<HomeWrapperWidget> {
?.copyWith(fontSize: 12),
selectedItemColor: MeditoColors.walterWhite,
unselectedItemColor: MeditoColors.newGrey,
currentIndex: _currentIndex,
onTap: _onTabTapped,
items: [
BottomNavigationBarItem(
tooltip: 'Home',
icon: Icon(
Icons.home_outlined,
size: 20,
currentIndex: _currentIndex,
onTap: _onTabTapped,
items: [
BottomNavigationBarItem(
tooltip: 'Home',
icon: Icon(
Icons.home_outlined,
size: 20,
),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(
Icons.format_list_bulleted_outlined,
size: 20,
),
tooltip: 'Packs',
label: 'Packs',
),
],
),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(
Icons.format_list_bulleted_outlined,
size: 20,
),
tooltip: 'Packs',
label: 'Packs',
),
],
),
),
);
}

View File

@ -81,8 +81,9 @@ class _ChooseBackgroundSoundDialogState
_handler = AudioHandlerInheritedWidget.of(context).audioHandler;
return StreamBuilder(
stream: Stream.fromFuture(Connectivity()
.checkConnectivity()),
stream: Stream.fromFuture(
Connectivity().checkConnectivity(),
),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data == ConnectivityResult.none) {

View File

@ -0,0 +1,327 @@
/*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 <https://www.gnu.org/licenses/>.*/
import 'dart:async';
import 'package:Medito/audioplayer/media_lib.dart';
import 'package:Medito/audioplayer/medito_audio_handler.dart';
import 'package:Medito/network/player/player_bloc.dart';
import 'package:Medito/utils/colors.dart';
import 'package:Medito/utils/shared_preferences_utils.dart';
import 'package:Medito/utils/strings.dart';
import 'package:Medito/utils/utils.dart';
import 'package:Medito/widgets/main/app_bar_widget.dart';
import 'package:Medito/widgets/player/player2/audio_complete_dialog.dart';
import 'package:Medito/widgets/player/position_indicator_widget.dart';
import 'package:Medito/widgets/player/subtitle_text_widget.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../../audioplayer/audio_inherited_widget.dart';
import '../../../tracking/tracking.dart';
import '../../../utils/bgvolume_utils.dart';
class NewPlayerWidget extends StatefulWidget {
final normalPop;
NewPlayerWidget({this.normalPop});
@override
_NewPlayerWidgetState createState() => _NewPlayerWidgetState();
}
class _NewPlayerWidgetState extends State<NewPlayerWidget> {
MeditoAudioHandler? _handler;
late PlayerBloc _bloc;
@override
void dispose() {
_handler?.stop();
_bloc.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_startTimeout();
_bloc = PlayerBloc();
Future.delayed(Duration(milliseconds: 200)).then((value) async {
var bgSound = await getBgSoundNameFromSharedPrefs();
var volume = await retrieveSavedBgVolume();
await _handler
?.customAction(SET_BG_SOUND_VOL, {SET_BG_SOUND_VOL: volume});
return _handler?.customAction(SEND_BG_SOUND, {SEND_BG_SOUND: bgSound});
});
}
void _startTimeout() {
var timerMaxSeconds = 20;
Timer.periodic(Duration(seconds: timerMaxSeconds), (timer) {
if (_handler?.playbackState.value.processingState ==
AudioProcessingState.loading &&
mounted) {
createSnackBar(TIMEOUT, context);
}
timer.cancel();
});
}
@override
Widget build(BuildContext context) {
_handler = AudioHandlerInheritedWidget.of(context).audioHandler;
var mediaItem = _handler?.mediaItem.value;
try {
if (_hasBGSound() == true) {
getSavedBgSoundData();
}
} on Exception catch (e, s) {
unawaited(
Sentry.captureException(e,
stackTrace: s, hint: 'extras[HAS_BG_SOUND]: ${_hasBGSound()}'),
);
}
_handler?.customEvent.stream.listen((event) {
if (mounted &&
event[AUDIO_COMPLETE] is bool &&
event[AUDIO_COMPLETE] == true) {
_trackSessionEnd(_handler?.mediaItem.value);
showGeneralDialog(
transitionDuration: Duration(milliseconds: 400),
context: context,
barrierColor: MeditoColors.darkMoon,
pageBuilder: (_, __, ___) {
return AudioCompleteDialog(
bloc: _bloc, mediaItem: _handler?.mediaItem.value);
});
}
});
return Material(
color: MeditoColors.greyIsTheNewBlack,
child: SafeArea(
top: false,
child: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_getAppBar(),
_buildImage(mediaItem?.artUri?.toString()),
Container(height: 24),
_buildTitleRows(),
Expanded(child: SizedBox.shrink()),
_buildPlayerButtonRow(mediaItem),
Expanded(child: SizedBox.shrink()),
// A seek bar.
PositionIndicatorWidget(
handler: _handler,
bgSoundsStream: _bloc.bgSoundsListController?.stream),
Container(height: 24)
])
],
),
),
);
}
bool? _hasBGSound() => _handler?.mediaItemHasBGSound();
StreamBuilder<bool> _buildPlayPauseButtons(MediaItem? mediaItem) {
return StreamBuilder<bool>(
stream: _handler?.playbackState.map((state) => state.playing).distinct(),
builder: (context, snapshot) {
final playing = snapshot.data ?? false;
if (playing) {
return _pauseButton();
} else {
return _playButton(mediaItem);
}
},
);
}
StreamBuilder<MediaItem> _buildTitleRows() {
return StreamBuilder<MediaItem>(
stream: _handler?.mediaItem.cast(),
builder: (context, snapshot) {
final mediaItem = snapshot.data;
return Column(
children: [_getTitleRow(mediaItem), _getSubtitleWidget(mediaItem)],
);
},
);
}
MeditoAppBarWidget _getAppBar() {
return MeditoAppBarWidget(
transparent: true,
hasCloseButton: true,
closePressed: _onBackPressed,
);
}
Widget _getTitleRow(MediaItem? mediaItem) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
child: Row(
children: [
Expanded(
child: Text(
mediaItem?.title ?? 'Loading...',
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: buildTitleTheme(),
)),
],
),
);
}
TextStyle? buildTitleTheme() {
return Theme.of(context).textTheme.headline1;
}
Widget _getSubtitleWidget(MediaItem? mediaItem) {
var attr = mediaItem?.extras != null ? (mediaItem?.extras?['attr']) : '';
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SubtitleTextWidget(body: attr),
);
}
Widget _playButton(MediaItem? mediaItem) => Semantics(
label: 'Play button',
child: IconButton(
color: MeditoColors.walterWhite,
icon: Icon(Icons.play_circle_fill),
iconSize: 80,
onPressed: () =>
_playPressed(mediaItem?.extras?[HAS_BG_SOUND] ?? true),
),
);
Future<void> _playPressed(bool hasBgSound) async {
await _handler?.play();
if (hasBgSound) await getSavedBgSoundData();
}
Widget _pauseButton() => Semantics(
label: 'Pause button',
child: IconButton(
icon: Icon(Icons.pause_circle_filled),
iconSize: 80,
color: MeditoColors.walterWhite,
onPressed: _handler?.pause,
),
);
Future<void> getSavedBgSoundData() async {
var file = await getBgSoundFileFromSharedPrefs();
var name = await getBgSoundNameFromSharedPrefs();
unawaited(_handler?.customAction(SEND_BG_SOUND, {SEND_BG_SOUND: name}));
unawaited(_handler?.customAction(PLAY_BG_SOUND, {PLAY_BG_SOUND: file}));
}
void _onBackPressed() {
GoRouter.of(context).pop();
}
Widget buildButtonLabel(MediaItem mediaItem) {
var label = _bloc.version?.buttonLabel;
if (label == null) return Container();
return Text(
label,
style: Theme.of(context).textTheme.subtitle2?.copyWith(
color: parseColor(mediaItem.extras?[SECONDARY_COLOUR])),
);
}
Widget buildSvgPicture(Color secondaryColor) {
var icon = _bloc.version?.buttonIcon;
if (icon == null) return Container();
return SvgPicture.asset(
'assets/images/' + icon + '.svg',
color: secondaryColor,
);
}
void _trackSessionEnd(MediaItem? mediaItem) {
if (mediaItem == null) return;
unawaited(
Tracking.postUsage(
Tracking.AUDIO_COMPLETED,
{
Tracking.SESSION_ID: mediaItem.extras?[SESSION_ID].toString() ?? '',
Tracking.SESSION_DURATION: mediaItem.extras?[LENGTH].toString() ?? '',
Tracking.SESSION_GUIDE: mediaItem.artist ?? ''
},
),
);
}
Widget _buildPlayerButtonRow(MediaItem? mediaItem) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildRewindButton(),
Container(width: 38),
_buildPlayPauseButtons(mediaItem),
Container(width: 38),
_buildForwardButton()
],
);
}
Widget _buildRewindButton() {
return IconButton(
iconSize: 40,
color: MeditoColors.walterWhite,
onPressed: () => _handler?.skipBackward10Secs(),
icon: Icon(Icons.replay_10));
}
Widget _buildForwardButton() {
return IconButton(
iconSize: 40,
color: MeditoColors.walterWhite,
onPressed: () => _handler?.skipForward30Secs(),
icon: Icon(Icons.forward_30));
}
Widget _buildImage(String? currentImage) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6))),
child: getNetworkImageWidget(currentImage ?? ''),
),
);
}
}

View File

@ -1,380 +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 <https://www.gnu.org/licenses/>.*/
import 'dart:async';
import 'package:Medito/audioplayer/media_lib.dart';
import 'package:Medito/audioplayer/medito_audio_handler.dart';
import 'package:Medito/network/player/player_bloc.dart';
import 'package:Medito/utils/colors.dart';
import 'package:Medito/utils/shared_preferences_utils.dart';
import 'package:Medito/utils/strings.dart';
import 'package:Medito/utils/utils.dart';
import 'package:Medito/widgets/main/app_bar_widget.dart';
import 'package:Medito/widgets/player/player2/audio_complete_dialog.dart';
import 'package:Medito/widgets/player/player_button.dart';
import 'package:Medito/widgets/player/position_indicator_widget.dart';
import 'package:Medito/widgets/player/subtitle_text_widget.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../../audioplayer/audio_inherited_widget.dart';
import '../../../tracking/tracking.dart';
import '../../../utils/bgvolume_utils.dart';
import '../background_sounds_sheet_widget.dart';
class PlayerWidget extends StatefulWidget {
final normalPop;
PlayerWidget({this.normalPop});
@override
_PlayerWidgetState createState() => _PlayerWidgetState();
}
class _PlayerWidgetState extends State<PlayerWidget> {
MeditoAudioHandler? _handler;
late PlayerBloc _bloc;
@override
void dispose() {
_handler?.stop();
_bloc.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_startTimeout();
_bloc = PlayerBloc();
Future.delayed(Duration(milliseconds: 200)).then((value) async {
var bgSound = await getBgSoundNameFromSharedPrefs();
var volume = await retrieveSavedBgVolume();
await _handler
?.customAction(SET_BG_SOUND_VOL, {SET_BG_SOUND_VOL: volume});
return _handler?.customAction(SEND_BG_SOUND, {SEND_BG_SOUND: bgSound});
});
}
void _startTimeout() {
var timerMaxSeconds = 20;
Timer.periodic(Duration(seconds: timerMaxSeconds), (timer) {
if (_handler?.playbackState.value.processingState ==
AudioProcessingState.loading &&
mounted) {
createSnackBar(TIMEOUT, context);
}
timer.cancel();
});
}
@override
Widget build(BuildContext context) {
_handler = AudioHandlerInheritedWidget.of(context).audioHandler;
var mediaItem = _handler?.mediaItem.value;
try {
if (_handler?.mediaItem.value?.extras?[HAS_BG_SOUND]) {
getSavedBgSoundData();
}
} on Exception catch (e, s) {
unawaited(Sentry.captureException(e,
stackTrace: s,
hint:
'extras[HAS_BG_SOUND]: ${_handler?.mediaItem.value?.extras?[HAS_BG_SOUND]}'));
}
_handler?.customEvent.stream.listen((event) {
if (mounted &&
event[AUDIO_COMPLETE] is bool &&
event[AUDIO_COMPLETE] == true) {
_trackSessionEnd(_handler?.mediaItem.value);
showGeneralDialog(
transitionDuration: Duration(milliseconds: 400),
context: context,
barrierColor: MeditoColors.darkMoon,
pageBuilder: (_, __, ___) {
return AudioCompleteDialog(
bloc: _bloc, mediaItem: _handler?.mediaItem.value);
});
}
});
return Material(
child: SafeArea(
top: false,
child: Stack(
children: [
_getGradientWidget(mediaItem, context),
_getGradientOverlayWidget(),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_getAppBar(),
// Show media item title
StreamBuilder<MediaItem>(
stream: _handler?.mediaItem.cast(),
builder: (context, snapshot) {
final mediaItem = snapshot.data;
return Column(
children: [
_getTitleRow(mediaItem, false),
_getSubtitleWidget(mediaItem, false)
],
);
},
),
Expanded(child: SizedBox.shrink()),
StreamBuilder<bool>(
stream: _handler?.playbackState
.map((state) => state.playing)
.distinct(),
builder: (context, snapshot) {
final playing = snapshot.data ?? false;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (playing)
_pauseButton(mediaItem)
else
_playButton(mediaItem),
],
),
_getBgMusicIconButton(
mediaItem?.extras?[HAS_BG_SOUND] ?? true)
],
);
},
),
Expanded(child: SizedBox.shrink()),
// A seek bar.
PositionIndicatorWidget(
handler: _handler,
color: parseColor(mediaItem?.extras?[PRIMARY_COLOUR]),
),
Container(height: 24)
])
],
),
),
);
}
Widget _getGradientOverlayWidget() {
return Image.asset(
'assets/images/texture.png',
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
fit: BoxFit.fill,
);
}
Widget _getGradientWidget(MediaItem? mediaItem, BuildContext context) {
return Align(
alignment: Alignment.center,
child: Container(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
parseColor(mediaItem?.extras?[PRIMARY_COLOUR]).withAlpha(100),
parseColor(mediaItem?.extras?[PRIMARY_COLOUR]).withAlpha(0),
],
radius: 1.0,
)),
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
));
}
MeditoAppBarWidget _getAppBar() {
return MeditoAppBarWidget(
transparent: true,
hasCloseButton: true,
closePressed: _onBackPressed,
);
}
Widget _getTitleRow(MediaItem? mediaItem, bool complete) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
child: Row(
children: [
Expanded(
child: !complete
? Text(
mediaItem?.title ?? 'Loading...',
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: buildTitleTheme(),
)
: FutureBuilder<String>(
future: _bloc.getVersionTitle(),
builder: (context, snapshot) {
return Text(
snapshot.hasData ? (snapshot.data ?? '') : 'Loading...',
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: buildTitleTheme(),
);
})),
],
),
);
}
TextStyle? buildTitleTheme() {
return Theme.of(context).textTheme.headline1;
}
Widget _getSubtitleWidget(MediaItem? mediaItem, bool complete) {
var attr = '';
if (complete) {
attr = _bloc.version?.body ?? WELL_DONE_SUBTITLE;
} else {
attr = mediaItem?.extras != null ? (mediaItem?.extras?['attr']) : '';
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SubtitleTextWidget(body: attr),
);
}
Widget _getBgMusicIconButton(bool visible) {
return Visibility(
visible: visible,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(top: 32),
child: MaterialButton(
enableFeedback: true,
textColor: MeditoColors.walterWhite,
color: MeditoColors.walterWhiteTrans,
onPressed: _onBgMusicPressed,
child: Padding(
padding: EdgeInsets.only(top: 4),
child: Row(children: [
Icon(
Icons.music_note_outlined,
color: MeditoColors.walterWhite,
),
Container(width: 8),
Text(SOUNDS),
Container(width: 8),
]))),
),
],
),
);
}
Widget _playButton(MediaItem? mediaItem) => Semantics(
label: 'Play button',
child: PlayerButton(
icon: Icons.play_arrow,
onPressed: () => _playPressed(mediaItem?.extras?[HAS_BG_SOUND] ?? true),
secondaryColor: parseColor(mediaItem?.extras?[SECONDARY_COLOUR]),
primaryColor: parseColor(mediaItem?.extras?[PRIMARY_COLOUR]),
),
);
Future<void> _playPressed(bool hasBgSound) async {
await _handler?.play();
if (hasBgSound) await getSavedBgSoundData();
}
Widget _pauseButton(MediaItem? mediaItem) => Semantics(
label: 'Pause button',
child: PlayerButton(
icon: Icons.pause,
secondaryColor: parseColor(mediaItem?.extras?[SECONDARY_COLOUR]),
primaryColor: parseColor(mediaItem?.extras?[PRIMARY_COLOUR]),
onPressed: _handler?.pause,
),
);
Future<void> getSavedBgSoundData() async {
var file = await getBgSoundFileFromSharedPrefs();
var name = await getBgSoundNameFromSharedPrefs();
unawaited(_handler?.customAction(SEND_BG_SOUND, {SEND_BG_SOUND: name}));
unawaited(_handler?.customAction(PLAY_BG_SOUND, {PLAY_BG_SOUND: file}));
}
void _onBackPressed() {
GoRouter.of(context).pop();
}
Widget buildButtonLabel(MediaItem mediaItem) {
var label = _bloc.version?.buttonLabel;
if (label == null) return Container();
return Text(
label,
style: Theme.of(context).textTheme.subtitle2?.copyWith(
color: parseColor(mediaItem.extras?[SECONDARY_COLOUR])),
);
}
Widget buildSvgPicture(Color secondaryColor) {
var icon = _bloc.version?.buttonIcon;
if (icon == null) return Container();
return SvgPicture.asset(
'assets/images/' + icon + '.svg',
color: secondaryColor,
);
}
void _trackSessionEnd(MediaItem? mediaItem) {
if (mediaItem == null) return;
unawaited(
Tracking.postUsage(
Tracking.AUDIO_COMPLETED,
{
Tracking.SESSION_ID: mediaItem.extras?[SESSION_ID],
Tracking.SESSION_DURATION: mediaItem.extras?[LENGTH],
Tracking.SESSION_GUIDE: mediaItem.artist ?? ''
},
),
);
}
void _onBgMusicPressed() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ChooseBackgroundSoundDialog(
handler: _handler, stream: _bloc.bgSoundsListController?.stream),
);
// slight delay in case the cache returns before the sheet opens
Future.delayed(Duration(milliseconds: 50))
.then((value) => _bloc.fetchBackgroundSounds());
}
}

View File

@ -1,49 +0,0 @@
import 'package:Medito/utils/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class PlayerButton extends StatelessWidget {
final Widget? child;
final IconData? icon;
final Future<void> Function()? onPressed;
final Color? primaryColor;
final Color? secondaryColor;
final String? text;
final SvgPicture? image;
PlayerButton(
{Key? key,
this.child,
this.icon,
this.onPressed,
this.primaryColor,
this.text,
this.secondaryColor,
this.image})
: super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
'assets/images/blurb.svg',
height: 88,
width: 88,
color: primaryColor,
),
image ??
Icon(
icon,
size: 24,
color: secondaryColor ?? MeditoColors.darkMoon,
),
child ?? Container()
],
),
);
}
}

View File

@ -2,16 +2,19 @@ import 'dart:math';
import 'package:Medito/audioplayer/media_lib.dart';
import 'package:Medito/audioplayer/medito_audio_handler.dart';
import 'package:Medito/network/player/player_bloc.dart';
import 'package:Medito/utils/colors.dart';
import 'package:Medito/utils/duration_ext.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
class PositionIndicatorWidget extends StatefulWidget {
final Color? color;
final MeditoAudioHandler? handler;
import 'background_sounds_sheet_widget.dart';
PositionIndicatorWidget({Key? key, this.handler, this.color})
class PositionIndicatorWidget extends StatefulWidget {
final MeditoAudioHandler? handler;
final Stream? bgSoundsStream;
PositionIndicatorWidget({Key? key, this.handler, this.bgSoundsStream})
: super(key: key);
@override
@ -20,6 +23,8 @@ class PositionIndicatorWidget extends StatefulWidget {
}
class _PositionIndicatorWidgetState extends State<PositionIndicatorWidget> {
final _speedList = ['X1', 'X1.25', 'X1.5', 'X2', 'X0.6'];
var _currentSpeed = 'X1';
/// Tracks the position while the user drags the seek bar.
final BehaviorSubject<double?> _dragPositionSubject =
@ -27,13 +32,13 @@ class _PositionIndicatorWidgetState extends State<PositionIndicatorWidget> {
@override
Widget build(BuildContext context) {
double? _seekPos;
return StreamBuilder(
stream: Rx.combineLatest2<double?, double, double?>(
_dragPositionSubject.stream,
Stream.periodic(Duration(milliseconds: 200), (count) => count.toDouble()),
Stream.periodic(
Duration(milliseconds: 200), (count) => count.toDouble()),
(dragPosition, _) => dragPosition),
builder: (context, AsyncSnapshot<double?> snapshot) {
//snapshot.data will be non null if slider was dragged
@ -51,14 +56,14 @@ class _PositionIndicatorWidgetState extends State<PositionIndicatorWidget> {
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
child: SliderTheme(
data: SliderThemeData(
trackHeight: 4,
trackHeight: 8,
trackShape: CustomTrackShape(),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 12.0),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 0.0),
),
child: Slider(
min: 0.0,
activeColor: widget.color,
inactiveColor: MeditoColors.meditoTextGrey,
activeColor: MeditoColors.walterWhite,
inactiveColor: MeditoColors.greyIsTheNewGrey,
max: duration.toDouble(),
value: _seekPos ??
max(0.0, min(position.toDouble(), duration.toDouble())),
@ -66,7 +71,8 @@ class _PositionIndicatorWidgetState extends State<PositionIndicatorWidget> {
_dragPositionSubject.add(value);
},
onChangeEnd: (value) {
widget.handler?.seek(Duration(milliseconds: value.toInt()));
widget.handler
?.seek(Duration(milliseconds: value.toInt()));
_seekPos = value;
_dragPositionSubject.add(null); // todo is this ok?
},
@ -84,26 +90,157 @@ class _PositionIndicatorWidgetState extends State<PositionIndicatorWidget> {
return Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
Duration(milliseconds: position).toMinutesSeconds(),
style: Theme.of(context)
.textTheme
.subtitle2
?.copyWith(color: MeditoColors.meditoTextGrey),
),
Text(
Duration(milliseconds: duration).toMinutesSeconds(),
style: Theme.of(context)
.textTheme
.subtitle2
?.copyWith(color: MeditoColors.meditoTextGrey),
),
_getCurrentPositionLabel(position, context),
Container(width: 8),
_getSpeedLabel(duration, context),
Container(width: 8),
if (_hasBGSound()) _getSoundLabel(),
if (_hasBGSound()) Container(width: 8),
_getFullDurationLabel(duration, context),
],
),
);
}
bool _hasBGSound() => widget.handler?.mediaItemHasBGSound() == true;
Expanded _getFullDurationLabel(int duration, BuildContext context) {
return Expanded(
flex: 25,
child: Container(
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
color: MeditoColors.greyIsTheNewGrey,
borderRadius: BorderRadius.all(Radius.circular(3)),
),
child: Text(
Duration(milliseconds: duration).toMinutesSeconds(),
style: Theme.of(context)
.textTheme
.subtitle2
?.copyWith(color: MeditoColors.walterWhite),
),
),
);
}
Expanded _getCurrentPositionLabel(int position, BuildContext context) {
return Expanded(
flex: 25,
child: Container(
alignment: Alignment.center,
height: 40,
decoration: BoxDecoration(
color: MeditoColors.greyIsTheNewGrey,
borderRadius: BorderRadius.all(Radius.circular(3)),
),
child: Text(
Duration(milliseconds: position).toMinutesSeconds(),
style: Theme.of(context)
.textTheme
.subtitle2
?.copyWith(color: MeditoColors.walterWhite),
),
),
);
}
Widget _getSpeedLabel(int duration, BuildContext context) {
return Expanded(
flex: 25,
child: GestureDetector(
onTap: () => {widget.handler?.setPlayerSpeed(_getNextSpeed())},
child: Container(
height: 40,
decoration: BoxDecoration(
color: MeditoColors.walterWhite,
borderRadius: BorderRadius.all(Radius.circular(3)),
),
alignment: Alignment.center,
child: Text(
_currentSpeed,
style: Theme.of(context)
.textTheme
.subtitle2
?.copyWith(color: MeditoColors.greyIsTheNewGrey),
),
),
),
);
}
Widget _getSoundLabel() {
return Builder(builder: (context) {
return GestureDetector(
onTap: () => _onBgMusicPressed(context),
child: Container(
height: 40,
padding: EdgeInsets.symmetric(horizontal: 24),
alignment: Alignment.center,
decoration: BoxDecoration(
color: MeditoColors.walterWhite,
borderRadius: BorderRadius.all(Radius.circular(3)),
),
child: Text(
'SOUND',
style: Theme.of(context)
.textTheme
.subtitle2
?.copyWith(color: MeditoColors.greyIsTheNewGrey),
),
),
);
});
}
double _getNextSpeed() {
var nextIndex = _speedList.indexOf(_currentSpeed) + 1;
if (nextIndex >= _speedList.length) {
_currentSpeed = _speedList[0];
} else {
_currentSpeed = _speedList[nextIndex];
}
setState(() {
_currentSpeed;
});
return _getSpeedDoubleFromString(_currentSpeed);
}
double _getSpeedDoubleFromString(String current) {
if (current == 'X1') {
return 1;
} else if (current == 'X1.25') {
return 1.25;
} else if (current == 'X1.5') {
return 1.5;
} else if (current == 'X2') {
return 2;
} else if (current == 'X0.6') {
return 0.6;
} else if (current == 'X0.75') {
return 1;
} else {
return 0.75;
}
}
void _onBgMusicPressed(BuildContext context) {
var bloc = PlayerBloc();
// slight delay in case the cache returns before the sheet opens
Future.delayed(Duration(milliseconds: 50))
.then((value) => bloc.fetchBackgroundSounds());
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ChooseBackgroundSoundDialog(
handler: widget.handler, stream: bloc.bgSoundsListController?.stream),
);
}
}
class CustomTrackShape extends RoundedRectSliderTrackShape {
@ -123,7 +260,7 @@ class CustomTrackShape extends RoundedRectSliderTrackShape {
final trackLeft = offset.dx;
var trackTop;
if (addTopPadding) {
trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2 + 8;
trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2 + 12;
} else {
trackTop = parentBox.size.height / 2 - 2;
}

View File

@ -19,7 +19,7 @@ dependencies:
flutter:
sdk: flutter
http: ^0.13.3
google_fonts: ^2.3.1
google_fonts: ^3.0.1
flutter_svg: ^1.0.3
path_provider: ^2.0.1
url_launcher: ^6.0.3
@ -40,10 +40,17 @@ dependencies:
sentry_flutter: ^6.9.1
back_button_interceptor: ^5.0.0
go_router: ^3.0.4
flutter_riverpod: ^2.1.1
freezed: ^2.2.0
freezed_annotation: ^2.2.0
json_annotation: ^4.7.0
dev_dependencies:
build_runner: ^2.3.0
riverpod_generator: ^1.0.4
flutter_test:
sdk: flutter
json_serializable: ^6.5.4
mocktail: ^0.3.0