Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom ringtones and melodies #75

Merged
merged 6 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions lib/alarm/data/alarm_settings_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import 'package:clock_app/alarm/types/schedules/weekly_alarm_schedule.dart';
import 'package:clock_app/alarm/widgets/alarm_task_card.dart';
import 'package:clock_app/alarm/widgets/try_alarm_task_button.dart';
import 'package:clock_app/audio/audio_channels.dart';
import 'package:clock_app/audio/logic/audio_session.dart';
import 'package:clock_app/audio/types/audio.dart';
import 'package:clock_app/audio/types/ringtone_player.dart';
import 'package:clock_app/audio/types/ringtone_manager.dart';
import 'package:clock_app/common/types/file_item.dart';
import 'package:clock_app/common/utils/ringtones.dart';
import 'package:clock_app/settings/types/setting.dart';
import 'package:clock_app/settings/types/setting_group.dart';
import 'package:clock_app/timer/types/time_duration.dart';
Expand Down Expand Up @@ -93,13 +92,9 @@ SettingGroup alarmSettingsSchema = SettingGroup(
SettingGroup(
"Sound",
[
DynamicSelectSetting<Audio>(
DynamicSelectSetting<FileItem>(
"Melody",
() => RingtoneManager.ringtones
.map((ringtone) =>
SelectSettingOption<Audio>(ringtone.title, ringtone))
.toList(),
onSelect: (context, index, uri) {},
getRingtoneOptions,
onChange: (context, index) {
RingtonePlayer.stop();
},
Expand All @@ -109,7 +104,6 @@ SettingGroup alarmSettingsSchema = SettingGroup(
"Audio Channel", audioChannelOptions,
onChange: (context, index) {
RingtonePlayer.stop();
initializeAudioSession(audioChannelOptions[index].value);
}),
SliderSetting("Volume", 0, 100, 100, unit: "%"),
SwitchSetting("Rising Volume", false,
Expand Down
4 changes: 1 addition & 3 deletions lib/alarm/logic/alarm_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,12 @@ void triggerScheduledNotification(int scheduleId, Json params) async {

await initializeAppDataDirectory();
await GetStorage.init();
await RingtoneManager.initialize();
// await RingtoneManager.initialize();
await RingtonePlayer.initialize();

if (notificationType == ScheduledNotificationType.alarm) {
await initializeAudioSession(getAlarmByScheduleId(scheduleId).audioChannel);
triggerAlarm(scheduleId, params);
} else if (notificationType == ScheduledNotificationType.timer) {
await initializeAudioSession(getTimerById(scheduleId).audioChannel);
triggerTimer(scheduleId, params);
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/alarm/logic/handle_boot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ void handleBoot() async {
.writeAsStringSync(message, mode: FileMode.append);

await initializeAppDataDirectory();
await RingtoneManager.initialize();
// await RingtoneManager.initialize();

updateAlarms();
}
3 changes: 2 additions & 1 deletion lib/alarm/types/alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:clock_app/alarm/types/schedules/dates_alarm_schedule.dart';
import 'package:clock_app/alarm/types/schedules/once_alarm_schedule.dart';
import 'package:clock_app/alarm/types/schedules/range_alarm_schedule.dart';
import 'package:clock_app/alarm/types/schedules/weekly_alarm_schedule.dart';
import 'package:clock_app/common/types/file_item.dart';
import 'package:clock_app/common/types/time.dart';
import 'package:clock_app/audio/types/audio.dart';
import 'package:clock_app/common/types/json.dart';
Expand Down Expand Up @@ -70,7 +71,7 @@ class Alarm extends CustomizableListItem {
SettingGroup get settings => _settings;
String get label => _settings.getSetting("Label").value;
Type get scheduleType => _settings.getSetting("Type").value;
Audio get ringtone => _settings.getSetting("Melody").value;
FileItem get ringtone => _settings.getSetting("Melody").value;
bool get vibrate => _settings.getSetting("Vibration").value;
double get snoozeLength => _settings.getSetting("Length").value;
List<AlarmTask> get tasks => _settings.getSetting("Tasks").value;
Expand Down
9 changes: 9 additions & 0 deletions lib/audio/logic/system_ringtones.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:clock_app/common/types/file_item.dart';
import 'package:flutter_system_ringtones/flutter_system_ringtones.dart';

Future<List<FileItem>> getSystemRingtones() async {
return (await FlutterSystemRingtones.getAlarmSounds())
.map((ringtone) =>
FileItem(ringtone.title, ringtone.uri, isDeletable: false))
.toList();
}
76 changes: 38 additions & 38 deletions lib/audio/types/audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,46 @@ import 'dart:convert';

import 'package:clock_app/common/types/json.dart';

class Audio {
final String id;
final String title;
final String uri;
// class Audio {
// final String id;
// final String title;
// final String uri;

const Audio({
required this.id,
required this.title,
required this.uri,
});
// const Audio({
// required this.id,
// required this.title,
// required this.uri,
// });

factory Audio.fromJson(Json json) => Audio(
id: json != null ? json['id'] ?? '' : '',
title: json != null ? json['title'] ?? 'Unknown' : 'Unknown',
uri: json != null ? json['uri'] ?? '' : '',
);
// factory Audio.fromJson(Json json) => Audio(
// id: json != null ? json['id'] ?? '' : '',
// title: json != null ? json['title'] ?? 'Unknown' : 'Unknown',
// uri: json != null ? json['uri'] ?? '' : '',
// );

Json toJson() => {
'id': id,
'title': title,
'uri': uri,
};
// Json toJson() => {
// 'id': id,
// 'title': title,
// 'uri': uri,
// };

factory Audio.fromEncodedJson(String encodedJson) =>
Audio.fromJson(json.decode(encodedJson));
String toEncodedJson() => json.encode(toJson());
@override
String toString() {
return 'Ringtone{id: $id, title: $title, uri: $uri}';
}
// factory Audio.fromEncodedJson(String encodedJson) =>
// Audio.fromJson(json.decode(encodedJson));
// String toEncodedJson() => json.encode(toJson());
// @override
// String toString() {
// return 'Ringtone{id: $id, title: $title, uri: $uri}';
// }

Audio copyWith({
String? id,
String? title,
String? uri,
}) {
return Audio(
id: id ?? this.id,
title: title ?? this.title,
uri: uri ?? this.uri,
);
}
}
// Audio copyWith({
// String? id,
// String? title,
// String? uri,
// }) {
// return Audio(
// id: id ?? this.id,
// title: title ?? this.title,
// uri: uri ?? this.uri,
// );
// }
// }
29 changes: 19 additions & 10 deletions lib/audio/types/ringtone_manager.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import 'package:clock_app/audio/types/audio.dart';
import 'package:clock_app/common/types/file_item.dart';
import 'package:clock_app/common/utils/list_storage.dart';
import 'package:clock_app/settings/data/settings_schema.dart';
import 'package:flutter_system_ringtones/flutter_system_ringtones.dart';

class RingtoneManager {
static List<Audio> _ringtones = [];
// static List<FileItem> _ringtones = [];
// static List<FileItem> _customRingtones = [];
static String _lastPlayedRingtoneUri = "";

static final List<void Function()> _listeners = [];

static List<Audio> get ringtones => _ringtones;
// static List<FileItem> get ringtones => _ringtones;
// static List<FileItem> get customRingtones => _customRingtones;
static List<void Function()> get listeners => _listeners;
static String get lastPlayedRingtoneUri => _lastPlayedRingtoneUri;
static set lastPlayedRingtoneUri(String uri) {
Expand All @@ -25,12 +30,16 @@ class RingtoneManager {
_listeners.remove(listener);
}

static Future<void> initialize() async {
if (_ringtones.isEmpty) {
_ringtones = (await FlutterSystemRingtones.getAlarmSounds())
.map((ringtone) =>
Audio(id: ringtone.id, title: ringtone.title, uri: ringtone.uri))
.toList();
}
}
// static Future<void> updateCustomRingtones() async {
// _customRingtones = await loadList<FileItem>('ringtones');
// }

// static Future<void> initialize() async {
// if (_ringtones.isEmpty) {
// _ringtones = await getSystemRingtones();
// }
// if (_customRingtones.isEmpty) {
// _customRingtones = await loadList<FileItem>('ringtones');
// }
// }
}
10 changes: 9 additions & 1 deletion lib/audio/types/ringtone_player.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:audio_session/audio_session.dart';
import 'package:clock_app/alarm/types/alarm.dart';
import 'package:clock_app/audio/logic/audio_session.dart';
import 'package:clock_app/audio/types/ringtone_manager.dart';
import 'package:clock_app/timer/types/timer.dart';
import 'package:clock_app/timer/utils/timer_id.dart';
import 'package:just_audio/just_audio.dart';
import 'package:vibration/vibration.dart';

Expand All @@ -17,13 +20,17 @@ class RingtonePlayer {
}

static Future<void> playUri(String ringtoneUri,
{bool vibrate = false, LoopMode loopMode = LoopMode.one}) async {
{bool vibrate = false,
LoopMode loopMode = LoopMode.one,
AndroidAudioUsage channel = AndroidAudioUsage.media}) async {
await initializeAudioSession(channel);
activePlayer = _alarmPlayer;
await _play(ringtoneUri, vibrate: vibrate, loopMode: LoopMode.one);
}

static Future<void> playAlarm(Alarm alarm,
{LoopMode loopMode = LoopMode.one}) async {
await initializeAudioSession(alarm.audioChannel);
activePlayer = _alarmPlayer;
await _play(
alarm.ringtone.uri,
Expand All @@ -35,6 +42,7 @@ class RingtonePlayer {

static Future<void> playTimer(ClockTimer timer,
{LoopMode loopMode = LoopMode.one}) async {
await initializeAudioSession(timer.audioChannel);
activePlayer = _timerPlayer;
await _play(
timer.ringtone.uri,
Expand Down
15 changes: 11 additions & 4 deletions lib/common/data/paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;

const _appDataDirectory = "Clock";
const _ringtonesDirectory = "ringtones";
String _appDataDirectoryPath = "";

Future<void> initializeAppDataDirectory() async {
Expand All @@ -12,6 +13,8 @@ Future<void> initializeAppDataDirectory() async {
if (!await Directory(_appDataDirectoryPath).exists()) {
await Directory(_appDataDirectoryPath).create();
}

await Directory(getRingtonesDirectoryPathSync()).create(recursive: true);
}

String getAppDataDirectoryPathSync() {
Expand All @@ -27,10 +30,14 @@ Future<String> getAppDataDirectoryPath() async {
(await getApplicationDocumentsDirectory()).path, _appDataDirectory);
}

Future<String> getRingtonesDirectoryPath() async {
return path.join(await getAppDataDirectoryPath(), _ringtonesDirectory);
}

String getRingtonesDirectoryPathSync() {
return path.join(getAppDataDirectoryPathSync(), _ringtonesDirectory);
}

Future<String> getTimezonesDatabasePath() async {
return path.join(await getAppDataDirectoryPath(), 'timezones.db');
}

// Future<String> getMainDatabasePath() async {
// return path.join(await getDatabasesPath(), 'database.db');
// }
49 changes: 21 additions & 28 deletions lib/common/types/file_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,48 @@ enum FileItemType {

class FileItem extends ListItem {
final int _id;
final String title;
final String uri;
final String name;
String _uri;
final bool _isDeletable;

set uri(String value) {
_uri = value;
}

String get uri => _uri;
@override
int get id => _id;

@override
bool get isDeletable => true;
bool get isDeletable => _isDeletable;

FileItem(
this.title,
this.uri,
) : _id = UniqueKey().hashCode;
FileItem(this.name, this._uri, {isDeletable = true})
: _id = UniqueKey().hashCode,
_isDeletable = isDeletable;

@override
FileItem.fromJson(Json json)
: _id = json != null
? json['_id'] ?? UniqueKey().hashCode
? json['id'] ?? UniqueKey().hashCode
: UniqueKey().hashCode,
title = json != null ? json['title'] ?? 'Unknown' : 'Unknown',
uri = json != null ? json['uri'] ?? '' : '';
name = json != null ? json['title'] ?? 'Unknown' : 'Unknown',
_uri = json != null ? json['uri'] ?? '' : '',
_isDeletable = json != null ? json['isDeletable'] ?? true : true;

@override
Json toJson() => {
'id': id,
'title': title,
'uri': uri,
'id': _id,
'title': name,
'uri': _uri,
'isDeletable': _isDeletable,
};

// factory Audio.fromEncodedJson(String encodedJson) =>
// Audio.fromJson(json.decode(encodedJson));
// String toEncodedJson() => json.encode(toJson());
@override
String toString() {
return json.encode(toJson());
}

FileItem copyWith({
String? title,
String? uri,
}) {
return FileItem(
title ?? this.title,
uri ?? this.uri,
);
}

@override
copy() {
return FileItem(title, uri);
return FileItem(name, _uri, isDeletable: _isDeletable);
}
}
14 changes: 12 additions & 2 deletions lib/common/utils/list_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:clock_app/common/data/paths.dart';
import 'package:clock_app/common/types/json.dart';
import 'package:clock_app/common/utils/json_serialize.dart';
import 'package:path/path.dart' as path;
import 'package:path/path.dart';
import 'package:queue/queue.dart';

final queue = Queue();
Expand Down Expand Up @@ -32,9 +33,18 @@ Future<void> saveTextFile(String key, String content) async {
});
}

Future<String> saveRingtone(String id, String sourceUri) async {
String ringtonesDirectory = getRingtonesDirectoryPathSync();
File source = File(sourceUri);
String newPath = path.join(ringtonesDirectory, '$id.mp3');
await queue.add(() async {
await source.copy(newPath);
});
return newPath;
}

String loadTextFileSync<T extends JsonSerializable>(String key) {
String appDataDirectory = getAppDataDirectoryPathSync();
File file = File(path.join(appDataDirectory, '$key.txt'));
File file = File(path.join(getAppDataDirectoryPathSync(), '$key.txt'));
try {
return file.readAsStringSync();
} catch (error) {
Expand Down
Loading