feat: build v1 Mews client: reader UI, gestures, Drift store, fetch actor
This commit is contained in:
parent
d5a0fcbf48
commit
01a36e8aee
56 changed files with 7287 additions and 2 deletions
157
app/lib/database/database.dart
Normal file
157
app/lib/database/database.dart
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
/// Visited pages, newest first; the most recent row is the app's start page.
|
||||
class HistoryItems extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get url => text()();
|
||||
TextColumn get title => text().nullable()();
|
||||
DateTimeColumn get visitedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
class Bookmarks extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get url => text()();
|
||||
TextColumn get title => text().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
/// A subscription to one page (spec 6). [entryUrls] is a JSON list of the
|
||||
/// entry URLs already seen, so a refresh can report only new posts.
|
||||
class Subscriptions extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get url => text()();
|
||||
TextColumn get title => text().nullable()();
|
||||
TextColumn get entryUrls => text().withDefault(const Constant('[]'))();
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
DateTimeColumn get lastCheckedAt => dateTime().nullable()();
|
||||
}
|
||||
|
||||
/// Single-row table (id 0) holding the reader's presentation choices.
|
||||
class ReaderSettings extends Table {
|
||||
IntColumn get id => integer().withDefault(const Constant(0))();
|
||||
TextColumn get font => text().withDefault(const Constant('Atkinson Hyperlegible'))();
|
||||
RealColumn get fontScale => real().withDefault(const Constant(1.0))();
|
||||
TextColumn get themeMode => text().withDefault(const Constant('system'))();
|
||||
TextColumn get variant => text().withDefault(const Constant('site'))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
/// The fetched source of a page, kept so the reader can reopen it offline.
|
||||
class CachedPages extends Table {
|
||||
TextColumn get url => text()();
|
||||
TextColumn get title => text().nullable()();
|
||||
TextColumn get source => text()();
|
||||
TextColumn get etag => text().nullable()();
|
||||
TextColumn get lastModified => text().nullable()();
|
||||
DateTimeColumn get cachedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {url};
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [HistoryItems, Bookmarks, Subscriptions, ReaderSettings, CachedPages])
|
||||
class MewsDatabase extends _$MewsDatabase {
|
||||
MewsDatabase() : super(_open());
|
||||
|
||||
/// For tests.
|
||||
MewsDatabase.test(super.executor);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
static QueryExecutor _open() => LazyDatabase(() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
return NativeDatabase.createInBackground(File(p.join(dir.path, 'mews.sqlite')));
|
||||
});
|
||||
|
||||
// History -------------------------------------------------------------
|
||||
|
||||
Future<void> recordVisit(String url, String? title) =>
|
||||
(into(historyItems).insert(HistoryItemsCompanion.insert(url: url, title: Value(title))));
|
||||
|
||||
Future<String?> lastVisitedUrl() async {
|
||||
final query = (select(historyItems)
|
||||
..orderBy([
|
||||
(r) => OrderingTerm.desc(r.visitedAt),
|
||||
// Tiebreak pages recorded within the same clock tick.
|
||||
(r) => OrderingTerm.desc(r.id),
|
||||
])
|
||||
..limit(1));
|
||||
final row = await query.getSingleOrNull();
|
||||
return row?.url;
|
||||
}
|
||||
|
||||
// Bookmarks -----------------------------------------------------------
|
||||
|
||||
Future<List<Bookmark>> allBookmarks() => select(bookmarks).get();
|
||||
|
||||
Future<void> addBookmark(String url, String? title) => into(bookmarks).insert(
|
||||
BookmarksCompanion.insert(url: url, title: Value(title)),
|
||||
mode: InsertMode.insertOrIgnore);
|
||||
|
||||
Future<void> removeBookmark(String url) =>
|
||||
(delete(bookmarks)..where((r) => r.url.equals(url))).go();
|
||||
|
||||
// Subscriptions ---------------------------------------------------------
|
||||
|
||||
Future<void> addSubscription(String url, String? title, List<String> entryUrls) => into(
|
||||
subscriptions)
|
||||
.insert(
|
||||
SubscriptionsCompanion.insert(
|
||||
url: url, title: Value(title), entryUrls: Value(jsonEncode(entryUrls))),
|
||||
mode: InsertMode.insertOrIgnore);
|
||||
|
||||
Future<void> removeSubscription(String url) =>
|
||||
(delete(subscriptions)..where((r) => r.url.equals(url))).go();
|
||||
|
||||
Future<List<Subscription>> allSubscriptions() => select(subscriptions).get();
|
||||
|
||||
Future<void> updateSubscription(String url,
|
||||
{String? title, List<String>? entryUrls, DateTime? lastCheckedAt}) =>
|
||||
(update(subscriptions)..where((r) => r.url.equals(url))).write(SubscriptionsCompanion(
|
||||
title: Value(title),
|
||||
entryUrls: entryUrls == null ? const Value.absent() : Value(jsonEncode(entryUrls)),
|
||||
lastCheckedAt: Value(lastCheckedAt),
|
||||
));
|
||||
|
||||
// Settings ---------------------------------------------------------------
|
||||
|
||||
Future<ReaderSetting?> readSettings() async {
|
||||
final query = select(readerSettings)..where((r) => r.id.equals(0));
|
||||
return query.getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> saveSettings({
|
||||
required String font,
|
||||
required double fontScale,
|
||||
required String themeMode,
|
||||
required String variant,
|
||||
}) =>
|
||||
into(readerSettings).insert(
|
||||
ReaderSettingsCompanion.insert(
|
||||
id: const Value(0),
|
||||
font: Value(font),
|
||||
fontScale: Value(fontScale),
|
||||
themeMode: Value(themeMode),
|
||||
variant: Value(variant),
|
||||
),
|
||||
mode: InsertMode.insertOrReplace);
|
||||
|
||||
// Cache --------------------------------------------------------------------
|
||||
|
||||
Future<CachedPage?> cachedPage(String url) async =>
|
||||
(select(cachedPages)..where((r) => r.url.equals(url))).getSingleOrNull();
|
||||
|
||||
Future<void> saveCachedPage(CachedPagesCompanion entry) =>
|
||||
into(cachedPages).insert(entry, mode: InsertMode.insertOrReplace);
|
||||
}
|
||||
2760
app/lib/database/database.g.dart
Normal file
2760
app/lib/database/database.g.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue