Skip to content
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
30 changes: 30 additions & 0 deletions lib/database/announcement.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:musbx/database/model.dart';
import 'package:musbx/utils/utils.dart';

part 'announcement.g.dart';

@JsonSerializable()
class Announcement extends Model {
/// An announcement shown to all users on startup.
Announcement({
super.id,
super.createdAt,
required this.title,
this.content,
});

/// The title of this announcement.
final String title;

/// The content of this announcement.
final String? content;

static Announcement fromJson(Json json) => _$AnnouncementFromJson(json);

@override
Json toJson() => _$AnnouncementToJson(this);

@override
String toString() => "Announcement($title)";
}
27 changes: 27 additions & 0 deletions lib/database/announcement.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions lib/database/database.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:musbx/keys.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class Database {
Database._();

/// The supabase client used internally.
static final SupabaseClient client = Supabase.instance.client;

/// Whether the database has been [initialize]d.
static bool isInitialized = false;

/// Initialize the database connection.
static Future<void> initialize() async {
if (isInitialized) return;

await Supabase.initialize(
url: supabaseUrl,
anonKey: supabaseAnonKey,
);

isInitialized = true;
}

/// The reference to the 'announcements' table.
static final SupabaseQueryBuilder announcements = Database.client.from(
"announcements",
);
}
27 changes: 27 additions & 0 deletions lib/database/model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:musbx/utils/utils.dart';
import 'package:uuid/uuid.dart';

/// A model with an [id] property that can be serialized to json and stored
/// on the database.
abstract class Model {
Model({
String? id,
DateTime? createdAt,
}) : id = id ?? Uuid().v4(),
createdAt = createdAt ?? DateTime.now();

/// The unique uuid of this object.
@JsonKey(required: true)
final String id;

/// When this object was created.
@JsonKey(required: true, name: "created_at")
final DateTime createdAt;

/// Serialize this object as json.
Json toJson();

@override
String toString() => "Model($id)";
}
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:material_plus/material_plus.dart';
import 'package:musbx/analytics.dart';
import 'package:musbx/database/database.dart';
import 'package:musbx/navigation.dart';
import 'package:musbx/songs/player/songs.dart';
import 'package:musbx/theme.dart';
Expand All @@ -21,6 +22,7 @@ Future<void> main() async {
await PersistentValue.initialize();
await Directories.initialize();

await Database.initialize();
await Analytics.initialize();
await Purchases.intialize();

Expand Down
6 changes: 6 additions & 0 deletions lib/navigation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:musbx/songs/song_page/song_page.dart';
import 'package:musbx/tuner/tuner_page.dart';
import 'package:musbx/utils/launch_handler.dart';
import 'package:musbx/utils/purchases.dart';
import 'package:musbx/widgets/announcements_page.dart';
import 'package:musbx/widgets/custom_icons.dart';
import 'package:musbx/widgets/exception_dialogs.dart';

Expand All @@ -33,6 +34,7 @@ class Routes {

static const String licenses = "/settings/licenses";
static const String contact = "/settings/contact";
static const String announcements = "/announcements";

/// The top-level shell branches.
static const List<String> branches = [metronome, library, tuner, drone];
Expand Down Expand Up @@ -114,6 +116,10 @@ class Navigation {
),
],
),
GoRoute(
path: Routes.announcements,
builder: (context, state) => AnnouncementsPage(),
),

StatefulShellRoute(
builder: _buildShell,
Expand Down
4 changes: 3 additions & 1 deletion lib/songs/library_page/library_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:musbx/songs/library_page/upload_file_button.dart';
import 'package:musbx/songs/player/library.dart';
import 'package:musbx/songs/player/song.dart';
import 'package:musbx/songs/player/songs.dart';
import 'package:musbx/widgets/announcements_page.dart';
import 'package:musbx/widgets/default_app_bar.dart';
import 'package:musbx/widgets/exception_dialogs.dart';

Expand All @@ -26,7 +27,8 @@ class LibraryPage extends StatelessWidget {
toolbarHeight: 68,
expandedHeight: 128,
title: LibrarySearchBar(),
actions: const [
actions: [
AnnouncementsButton(),
GetPremiumButton(),
SettingsButton(),
],
Expand Down
47 changes: 47 additions & 0 deletions lib/utils/announcements.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:material_plus/material_plus.dart';
import 'package:musbx/database/announcement.dart';
import 'package:musbx/database/database.dart';

class Announcements {
Announcements._();

/// The last time the announcements were read.
static final TransformedPersistentValue<DateTime, String> readAt =
TransformedPersistentValue(
"announcements/readAt",
initialValue: DateTime.now(),
from: (value) => DateTime.parse(value),
to: (value) => value.toIso8601String(),
);

/// Get the latest announcement from the database.
static Future<Announcement> getLatest() async {
return await Database.announcements
.select()
.order('created_at')
.limit(1)
.single()
.withConverter(Announcement.fromJson);
}

/// Get all announcements from the database.
static Future<List<Announcement>> getAll() async {
return await Database.announcements
.select()
.order('created_at')
.withConverter(
(data) => data.map(Announcement.fromJson).toList(),
);
}

/// Get all announcements from the database that have not been seen before.
static Future<List<Announcement>> getUnread() async {
return await Database.announcements
.select()
.gt("created_at", readAt.value.toIso8601String())
.order('created_at')
.withConverter(
(data) => data.map(Announcement.fromJson).toList(),
);
}
}
Loading
Loading