draft images, multiple bug fixes

This commit is contained in:
otsmr 2025-11-08 12:26:17 +01:00
parent aebf6de4a5
commit 80d6f85350
21 changed files with 261 additions and 28 deletions

View file

@ -156,7 +156,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
bool _showOnboarding = true; bool _showOnboarding = true;
bool _isLoaded = false; bool _isLoaded = false;
Future<int>? _proofOfWork; (Future<int>?, bool) _proofOfWork = (null, false);
@override @override
void initState() { void initState() {
@ -176,11 +176,14 @@ class _AppMainWidgetState extends State<AppMainWidget> {
if (!_isUserCreated && !_showDatabaseMigration) { if (!_isUserCreated && !_showDatabaseMigration) {
// This means the user is in the onboarding screen, so start with the Proof of Work. // This means the user is in the onboarding screen, so start with the Proof of Work.
final proof = await apiService.getProofOfWork(); final (proof, disabled) = await apiService.getProofOfWork();
if (proof != null) { if (proof != null) {
Log.info('Starting with proof of work calculation.'); Log.info('Starting with proof of work calculation.');
// Starting with the proof of work. // Starting with the proof of work.
_proofOfWork = calculatePoW(proof.prefix, proof.difficulty.toInt()); _proofOfWork =
(calculatePoW(proof.prefix, proof.difficulty.toInt()), false);
} else {
_proofOfWork = (null, disabled);
} }
} }

View file

@ -50,11 +50,27 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
.write(updates); .write(updates);
} }
Future<void> updateAllMediaFiles(
MediaFilesCompanion updates,
) async {
await update(mediaFiles).write(updates);
}
Future<MediaFile?> getMediaFileById(String mediaId) async { Future<MediaFile?> getMediaFileById(String mediaId) async {
return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId))) return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId)))
.getSingleOrNull(); .getSingleOrNull();
} }
Future<MediaFile?> getDraftMediaFile() async {
final medias = await (select(mediaFiles)
..where((t) => t.isDraftMedia.equals(true)))
.get();
if (medias.isEmpty) {
return null;
}
return medias.first;
}
Stream<MediaFile?> watchMedia(String mediaId) { Stream<MediaFile?> watchMedia(String mediaId) {
return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId))) return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId)))
.watchSingleOrNull(); .watchSingleOrNull();
@ -87,10 +103,9 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
Future<List<MediaFile>> getAllMediaFilesPendingUpload() async { Future<List<MediaFile>> getAllMediaFilesPendingUpload() async {
return (select(mediaFiles) return (select(mediaFiles)
..where( ..where(
(t) => (t) => (t.uploadState.equals(UploadState.initialized.name) |
t.uploadState.equals(UploadState.initialized.name) |
t.uploadState.equals(UploadState.uploadLimitReached.name) | t.uploadState.equals(UploadState.uploadLimitReached.name) |
t.uploadState.equals(UploadState.preprocessing.name), t.uploadState.equals(UploadState.preprocessing.name)),
)) ))
.get(); .get();
} }

View file

@ -44,10 +44,12 @@ class MediaFiles extends Table {
BoolColumn get requiresAuthentication => BoolColumn get requiresAuthentication =>
boolean().withDefault(const Constant(false))(); boolean().withDefault(const Constant(false))();
BoolColumn get reopenByContact => BoolColumn get reopenByContact =>
boolean().withDefault(const Constant(false))(); boolean().withDefault(const Constant(false))();
BoolColumn get stored => boolean().withDefault(const Constant(false))(); BoolColumn get stored => boolean().withDefault(const Constant(false))();
BoolColumn get isDraftMedia => boolean().withDefault(const Constant(false))();
TextColumn get reuploadRequestedBy => TextColumn get reuploadRequestedBy =>
text().map(IntListTypeConverter()).nullable()(); text().map(IntListTypeConverter()).nullable()();

View file

@ -1905,6 +1905,16 @@ class $MediaFilesTable extends MediaFiles
defaultConstraints: defaultConstraints:
GeneratedColumn.constraintIsAlways('CHECK ("stored" IN (0, 1))'), GeneratedColumn.constraintIsAlways('CHECK ("stored" IN (0, 1))'),
defaultValue: const Constant(false)); defaultValue: const Constant(false));
static const VerificationMeta _isDraftMediaMeta =
const VerificationMeta('isDraftMedia');
@override
late final GeneratedColumn<bool> isDraftMedia = GeneratedColumn<bool>(
'is_draft_media', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_draft_media" IN (0, 1))'),
defaultValue: const Constant(false));
@override @override
late final GeneratedColumnWithTypeConverter<List<int>?, String> late final GeneratedColumnWithTypeConverter<List<int>?, String>
reuploadRequestedBy = GeneratedColumn<String>( reuploadRequestedBy = GeneratedColumn<String>(
@ -1968,6 +1978,7 @@ class $MediaFilesTable extends MediaFiles
requiresAuthentication, requiresAuthentication,
reopenByContact, reopenByContact,
stored, stored,
isDraftMedia,
reuploadRequestedBy, reuploadRequestedBy,
displayLimitInMilliseconds, displayLimitInMilliseconds,
removeAudio, removeAudio,
@ -2009,6 +2020,12 @@ class $MediaFilesTable extends MediaFiles
context.handle(_storedMeta, context.handle(_storedMeta,
stored.isAcceptableOrUnknown(data['stored']!, _storedMeta)); stored.isAcceptableOrUnknown(data['stored']!, _storedMeta));
} }
if (data.containsKey('is_draft_media')) {
context.handle(
_isDraftMediaMeta,
isDraftMedia.isAcceptableOrUnknown(
data['is_draft_media']!, _isDraftMediaMeta));
}
if (data.containsKey('display_limit_in_milliseconds')) { if (data.containsKey('display_limit_in_milliseconds')) {
context.handle( context.handle(
_displayLimitInMillisecondsMeta, _displayLimitInMillisecondsMeta,
@ -2076,6 +2093,8 @@ class $MediaFilesTable extends MediaFiles
DriftSqlType.bool, data['${effectivePrefix}reopen_by_contact'])!, DriftSqlType.bool, data['${effectivePrefix}reopen_by_contact'])!,
stored: attachedDatabase.typeMapping stored: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}stored'])!, .read(DriftSqlType.bool, data['${effectivePrefix}stored'])!,
isDraftMedia: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}is_draft_media'])!,
reuploadRequestedBy: $MediaFilesTable.$converterreuploadRequestedByn reuploadRequestedBy: $MediaFilesTable.$converterreuploadRequestedByn
.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string,
data['${effectivePrefix}reupload_requested_by'])), data['${effectivePrefix}reupload_requested_by'])),
@ -2129,6 +2148,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
final bool requiresAuthentication; final bool requiresAuthentication;
final bool reopenByContact; final bool reopenByContact;
final bool stored; final bool stored;
final bool isDraftMedia;
final List<int>? reuploadRequestedBy; final List<int>? reuploadRequestedBy;
final int? displayLimitInMilliseconds; final int? displayLimitInMilliseconds;
final bool? removeAudio; final bool? removeAudio;
@ -2145,6 +2165,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
required this.requiresAuthentication, required this.requiresAuthentication,
required this.reopenByContact, required this.reopenByContact,
required this.stored, required this.stored,
required this.isDraftMedia,
this.reuploadRequestedBy, this.reuploadRequestedBy,
this.displayLimitInMilliseconds, this.displayLimitInMilliseconds,
this.removeAudio, this.removeAudio,
@ -2172,6 +2193,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
map['requires_authentication'] = Variable<bool>(requiresAuthentication); map['requires_authentication'] = Variable<bool>(requiresAuthentication);
map['reopen_by_contact'] = Variable<bool>(reopenByContact); map['reopen_by_contact'] = Variable<bool>(reopenByContact);
map['stored'] = Variable<bool>(stored); map['stored'] = Variable<bool>(stored);
map['is_draft_media'] = Variable<bool>(isDraftMedia);
if (!nullToAbsent || reuploadRequestedBy != null) { if (!nullToAbsent || reuploadRequestedBy != null) {
map['reupload_requested_by'] = Variable<String>($MediaFilesTable map['reupload_requested_by'] = Variable<String>($MediaFilesTable
.$converterreuploadRequestedByn .$converterreuploadRequestedByn
@ -2213,6 +2235,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
requiresAuthentication: Value(requiresAuthentication), requiresAuthentication: Value(requiresAuthentication),
reopenByContact: Value(reopenByContact), reopenByContact: Value(reopenByContact),
stored: Value(stored), stored: Value(stored),
isDraftMedia: Value(isDraftMedia),
reuploadRequestedBy: reuploadRequestedBy == null && nullToAbsent reuploadRequestedBy: reuploadRequestedBy == null && nullToAbsent
? const Value.absent() ? const Value.absent()
: Value(reuploadRequestedBy), : Value(reuploadRequestedBy),
@ -2254,6 +2277,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
serializer.fromJson<bool>(json['requiresAuthentication']), serializer.fromJson<bool>(json['requiresAuthentication']),
reopenByContact: serializer.fromJson<bool>(json['reopenByContact']), reopenByContact: serializer.fromJson<bool>(json['reopenByContact']),
stored: serializer.fromJson<bool>(json['stored']), stored: serializer.fromJson<bool>(json['stored']),
isDraftMedia: serializer.fromJson<bool>(json['isDraftMedia']),
reuploadRequestedBy: reuploadRequestedBy:
serializer.fromJson<List<int>?>(json['reuploadRequestedBy']), serializer.fromJson<List<int>?>(json['reuploadRequestedBy']),
displayLimitInMilliseconds: displayLimitInMilliseconds:
@ -2280,6 +2304,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
'requiresAuthentication': serializer.toJson<bool>(requiresAuthentication), 'requiresAuthentication': serializer.toJson<bool>(requiresAuthentication),
'reopenByContact': serializer.toJson<bool>(reopenByContact), 'reopenByContact': serializer.toJson<bool>(reopenByContact),
'stored': serializer.toJson<bool>(stored), 'stored': serializer.toJson<bool>(stored),
'isDraftMedia': serializer.toJson<bool>(isDraftMedia),
'reuploadRequestedBy': serializer.toJson<List<int>?>(reuploadRequestedBy), 'reuploadRequestedBy': serializer.toJson<List<int>?>(reuploadRequestedBy),
'displayLimitInMilliseconds': 'displayLimitInMilliseconds':
serializer.toJson<int?>(displayLimitInMilliseconds), serializer.toJson<int?>(displayLimitInMilliseconds),
@ -2300,6 +2325,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
bool? requiresAuthentication, bool? requiresAuthentication,
bool? reopenByContact, bool? reopenByContact,
bool? stored, bool? stored,
bool? isDraftMedia,
Value<List<int>?> reuploadRequestedBy = const Value.absent(), Value<List<int>?> reuploadRequestedBy = const Value.absent(),
Value<int?> displayLimitInMilliseconds = const Value.absent(), Value<int?> displayLimitInMilliseconds = const Value.absent(),
Value<bool?> removeAudio = const Value.absent(), Value<bool?> removeAudio = const Value.absent(),
@ -2318,6 +2344,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
requiresAuthentication ?? this.requiresAuthentication, requiresAuthentication ?? this.requiresAuthentication,
reopenByContact: reopenByContact ?? this.reopenByContact, reopenByContact: reopenByContact ?? this.reopenByContact,
stored: stored ?? this.stored, stored: stored ?? this.stored,
isDraftMedia: isDraftMedia ?? this.isDraftMedia,
reuploadRequestedBy: reuploadRequestedBy.present reuploadRequestedBy: reuploadRequestedBy.present
? reuploadRequestedBy.value ? reuploadRequestedBy.value
: this.reuploadRequestedBy, : this.reuploadRequestedBy,
@ -2352,6 +2379,9 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
? data.reopenByContact.value ? data.reopenByContact.value
: this.reopenByContact, : this.reopenByContact,
stored: data.stored.present ? data.stored.value : this.stored, stored: data.stored.present ? data.stored.value : this.stored,
isDraftMedia: data.isDraftMedia.present
? data.isDraftMedia.value
: this.isDraftMedia,
reuploadRequestedBy: data.reuploadRequestedBy.present reuploadRequestedBy: data.reuploadRequestedBy.present
? data.reuploadRequestedBy.value ? data.reuploadRequestedBy.value
: this.reuploadRequestedBy, : this.reuploadRequestedBy,
@ -2386,6 +2416,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
..write('requiresAuthentication: $requiresAuthentication, ') ..write('requiresAuthentication: $requiresAuthentication, ')
..write('reopenByContact: $reopenByContact, ') ..write('reopenByContact: $reopenByContact, ')
..write('stored: $stored, ') ..write('stored: $stored, ')
..write('isDraftMedia: $isDraftMedia, ')
..write('reuploadRequestedBy: $reuploadRequestedBy, ') ..write('reuploadRequestedBy: $reuploadRequestedBy, ')
..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ') ..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ')
..write('removeAudio: $removeAudio, ') ..write('removeAudio: $removeAudio, ')
@ -2407,6 +2438,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
requiresAuthentication, requiresAuthentication,
reopenByContact, reopenByContact,
stored, stored,
isDraftMedia,
reuploadRequestedBy, reuploadRequestedBy,
displayLimitInMilliseconds, displayLimitInMilliseconds,
removeAudio, removeAudio,
@ -2426,6 +2458,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
other.requiresAuthentication == this.requiresAuthentication && other.requiresAuthentication == this.requiresAuthentication &&
other.reopenByContact == this.reopenByContact && other.reopenByContact == this.reopenByContact &&
other.stored == this.stored && other.stored == this.stored &&
other.isDraftMedia == this.isDraftMedia &&
other.reuploadRequestedBy == this.reuploadRequestedBy && other.reuploadRequestedBy == this.reuploadRequestedBy &&
other.displayLimitInMilliseconds == this.displayLimitInMilliseconds && other.displayLimitInMilliseconds == this.displayLimitInMilliseconds &&
other.removeAudio == this.removeAudio && other.removeAudio == this.removeAudio &&
@ -2445,6 +2478,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
final Value<bool> requiresAuthentication; final Value<bool> requiresAuthentication;
final Value<bool> reopenByContact; final Value<bool> reopenByContact;
final Value<bool> stored; final Value<bool> stored;
final Value<bool> isDraftMedia;
final Value<List<int>?> reuploadRequestedBy; final Value<List<int>?> reuploadRequestedBy;
final Value<int?> displayLimitInMilliseconds; final Value<int?> displayLimitInMilliseconds;
final Value<bool?> removeAudio; final Value<bool?> removeAudio;
@ -2462,6 +2496,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
this.requiresAuthentication = const Value.absent(), this.requiresAuthentication = const Value.absent(),
this.reopenByContact = const Value.absent(), this.reopenByContact = const Value.absent(),
this.stored = const Value.absent(), this.stored = const Value.absent(),
this.isDraftMedia = const Value.absent(),
this.reuploadRequestedBy = const Value.absent(), this.reuploadRequestedBy = const Value.absent(),
this.displayLimitInMilliseconds = const Value.absent(), this.displayLimitInMilliseconds = const Value.absent(),
this.removeAudio = const Value.absent(), this.removeAudio = const Value.absent(),
@ -2480,6 +2515,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
this.requiresAuthentication = const Value.absent(), this.requiresAuthentication = const Value.absent(),
this.reopenByContact = const Value.absent(), this.reopenByContact = const Value.absent(),
this.stored = const Value.absent(), this.stored = const Value.absent(),
this.isDraftMedia = const Value.absent(),
this.reuploadRequestedBy = const Value.absent(), this.reuploadRequestedBy = const Value.absent(),
this.displayLimitInMilliseconds = const Value.absent(), this.displayLimitInMilliseconds = const Value.absent(),
this.removeAudio = const Value.absent(), this.removeAudio = const Value.absent(),
@ -2499,6 +2535,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
Expression<bool>? requiresAuthentication, Expression<bool>? requiresAuthentication,
Expression<bool>? reopenByContact, Expression<bool>? reopenByContact,
Expression<bool>? stored, Expression<bool>? stored,
Expression<bool>? isDraftMedia,
Expression<String>? reuploadRequestedBy, Expression<String>? reuploadRequestedBy,
Expression<int>? displayLimitInMilliseconds, Expression<int>? displayLimitInMilliseconds,
Expression<bool>? removeAudio, Expression<bool>? removeAudio,
@ -2518,6 +2555,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
'requires_authentication': requiresAuthentication, 'requires_authentication': requiresAuthentication,
if (reopenByContact != null) 'reopen_by_contact': reopenByContact, if (reopenByContact != null) 'reopen_by_contact': reopenByContact,
if (stored != null) 'stored': stored, if (stored != null) 'stored': stored,
if (isDraftMedia != null) 'is_draft_media': isDraftMedia,
if (reuploadRequestedBy != null) if (reuploadRequestedBy != null)
'reupload_requested_by': reuploadRequestedBy, 'reupload_requested_by': reuploadRequestedBy,
if (displayLimitInMilliseconds != null) if (displayLimitInMilliseconds != null)
@ -2540,6 +2578,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
Value<bool>? requiresAuthentication, Value<bool>? requiresAuthentication,
Value<bool>? reopenByContact, Value<bool>? reopenByContact,
Value<bool>? stored, Value<bool>? stored,
Value<bool>? isDraftMedia,
Value<List<int>?>? reuploadRequestedBy, Value<List<int>?>? reuploadRequestedBy,
Value<int?>? displayLimitInMilliseconds, Value<int?>? displayLimitInMilliseconds,
Value<bool?>? removeAudio, Value<bool?>? removeAudio,
@ -2558,6 +2597,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
requiresAuthentication ?? this.requiresAuthentication, requiresAuthentication ?? this.requiresAuthentication,
reopenByContact: reopenByContact ?? this.reopenByContact, reopenByContact: reopenByContact ?? this.reopenByContact,
stored: stored ?? this.stored, stored: stored ?? this.stored,
isDraftMedia: isDraftMedia ?? this.isDraftMedia,
reuploadRequestedBy: reuploadRequestedBy ?? this.reuploadRequestedBy, reuploadRequestedBy: reuploadRequestedBy ?? this.reuploadRequestedBy,
displayLimitInMilliseconds: displayLimitInMilliseconds:
displayLimitInMilliseconds ?? this.displayLimitInMilliseconds, displayLimitInMilliseconds ?? this.displayLimitInMilliseconds,
@ -2599,6 +2639,9 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
if (stored.present) { if (stored.present) {
map['stored'] = Variable<bool>(stored.value); map['stored'] = Variable<bool>(stored.value);
} }
if (isDraftMedia.present) {
map['is_draft_media'] = Variable<bool>(isDraftMedia.value);
}
if (reuploadRequestedBy.present) { if (reuploadRequestedBy.present) {
map['reupload_requested_by'] = Variable<String>($MediaFilesTable map['reupload_requested_by'] = Variable<String>($MediaFilesTable
.$converterreuploadRequestedByn .$converterreuploadRequestedByn
@ -2642,6 +2685,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
..write('requiresAuthentication: $requiresAuthentication, ') ..write('requiresAuthentication: $requiresAuthentication, ')
..write('reopenByContact: $reopenByContact, ') ..write('reopenByContact: $reopenByContact, ')
..write('stored: $stored, ') ..write('stored: $stored, ')
..write('isDraftMedia: $isDraftMedia, ')
..write('reuploadRequestedBy: $reuploadRequestedBy, ') ..write('reuploadRequestedBy: $reuploadRequestedBy, ')
..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ') ..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ')
..write('removeAudio: $removeAudio, ') ..write('removeAudio: $removeAudio, ')
@ -9161,6 +9205,7 @@ typedef $$MediaFilesTableCreateCompanionBuilder = MediaFilesCompanion Function({
Value<bool> requiresAuthentication, Value<bool> requiresAuthentication,
Value<bool> reopenByContact, Value<bool> reopenByContact,
Value<bool> stored, Value<bool> stored,
Value<bool> isDraftMedia,
Value<List<int>?> reuploadRequestedBy, Value<List<int>?> reuploadRequestedBy,
Value<int?> displayLimitInMilliseconds, Value<int?> displayLimitInMilliseconds,
Value<bool?> removeAudio, Value<bool?> removeAudio,
@ -9179,6 +9224,7 @@ typedef $$MediaFilesTableUpdateCompanionBuilder = MediaFilesCompanion Function({
Value<bool> requiresAuthentication, Value<bool> requiresAuthentication,
Value<bool> reopenByContact, Value<bool> reopenByContact,
Value<bool> stored, Value<bool> stored,
Value<bool> isDraftMedia,
Value<List<int>?> reuploadRequestedBy, Value<List<int>?> reuploadRequestedBy,
Value<int?> displayLimitInMilliseconds, Value<int?> displayLimitInMilliseconds,
Value<bool?> removeAudio, Value<bool?> removeAudio,
@ -9248,6 +9294,9 @@ class $$MediaFilesTableFilterComposer
ColumnFilters<bool> get stored => $composableBuilder( ColumnFilters<bool> get stored => $composableBuilder(
column: $table.stored, builder: (column) => ColumnFilters(column)); column: $table.stored, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get isDraftMedia => $composableBuilder(
column: $table.isDraftMedia, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<List<int>?, List<int>, String> ColumnWithTypeConverterFilters<List<int>?, List<int>, String>
get reuploadRequestedBy => $composableBuilder( get reuploadRequestedBy => $composableBuilder(
column: $table.reuploadRequestedBy, column: $table.reuploadRequestedBy,
@ -9331,6 +9380,10 @@ class $$MediaFilesTableOrderingComposer
ColumnOrderings<bool> get stored => $composableBuilder( ColumnOrderings<bool> get stored => $composableBuilder(
column: $table.stored, builder: (column) => ColumnOrderings(column)); column: $table.stored, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get isDraftMedia => $composableBuilder(
column: $table.isDraftMedia,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get reuploadRequestedBy => $composableBuilder( ColumnOrderings<String> get reuploadRequestedBy => $composableBuilder(
column: $table.reuploadRequestedBy, column: $table.reuploadRequestedBy,
builder: (column) => ColumnOrderings(column)); builder: (column) => ColumnOrderings(column));
@ -9394,6 +9447,9 @@ class $$MediaFilesTableAnnotationComposer
GeneratedColumn<bool> get stored => GeneratedColumn<bool> get stored =>
$composableBuilder(column: $table.stored, builder: (column) => column); $composableBuilder(column: $table.stored, builder: (column) => column);
GeneratedColumn<bool> get isDraftMedia => $composableBuilder(
column: $table.isDraftMedia, builder: (column) => column);
GeneratedColumnWithTypeConverter<List<int>?, String> GeneratedColumnWithTypeConverter<List<int>?, String>
get reuploadRequestedBy => $composableBuilder( get reuploadRequestedBy => $composableBuilder(
column: $table.reuploadRequestedBy, builder: (column) => column); column: $table.reuploadRequestedBy, builder: (column) => column);
@ -9471,6 +9527,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
Value<bool> requiresAuthentication = const Value.absent(), Value<bool> requiresAuthentication = const Value.absent(),
Value<bool> reopenByContact = const Value.absent(), Value<bool> reopenByContact = const Value.absent(),
Value<bool> stored = const Value.absent(), Value<bool> stored = const Value.absent(),
Value<bool> isDraftMedia = const Value.absent(),
Value<List<int>?> reuploadRequestedBy = const Value.absent(), Value<List<int>?> reuploadRequestedBy = const Value.absent(),
Value<int?> displayLimitInMilliseconds = const Value.absent(), Value<int?> displayLimitInMilliseconds = const Value.absent(),
Value<bool?> removeAudio = const Value.absent(), Value<bool?> removeAudio = const Value.absent(),
@ -9489,6 +9546,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
requiresAuthentication: requiresAuthentication, requiresAuthentication: requiresAuthentication,
reopenByContact: reopenByContact, reopenByContact: reopenByContact,
stored: stored, stored: stored,
isDraftMedia: isDraftMedia,
reuploadRequestedBy: reuploadRequestedBy, reuploadRequestedBy: reuploadRequestedBy,
displayLimitInMilliseconds: displayLimitInMilliseconds, displayLimitInMilliseconds: displayLimitInMilliseconds,
removeAudio: removeAudio, removeAudio: removeAudio,
@ -9507,6 +9565,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
Value<bool> requiresAuthentication = const Value.absent(), Value<bool> requiresAuthentication = const Value.absent(),
Value<bool> reopenByContact = const Value.absent(), Value<bool> reopenByContact = const Value.absent(),
Value<bool> stored = const Value.absent(), Value<bool> stored = const Value.absent(),
Value<bool> isDraftMedia = const Value.absent(),
Value<List<int>?> reuploadRequestedBy = const Value.absent(), Value<List<int>?> reuploadRequestedBy = const Value.absent(),
Value<int?> displayLimitInMilliseconds = const Value.absent(), Value<int?> displayLimitInMilliseconds = const Value.absent(),
Value<bool?> removeAudio = const Value.absent(), Value<bool?> removeAudio = const Value.absent(),
@ -9525,6 +9584,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
requiresAuthentication: requiresAuthentication, requiresAuthentication: requiresAuthentication,
reopenByContact: reopenByContact, reopenByContact: reopenByContact,
stored: stored, stored: stored,
isDraftMedia: isDraftMedia,
reuploadRequestedBy: reuploadRequestedBy, reuploadRequestedBy: reuploadRequestedBy,
displayLimitInMilliseconds: displayLimitInMilliseconds, displayLimitInMilliseconds: displayLimitInMilliseconds,
removeAudio: removeAudio, removeAudio: removeAudio,

View file

@ -817,5 +817,6 @@
"deleteChatAfterAWeek": "einer Woche.", "deleteChatAfterAWeek": "einer Woche.",
"deleteChatAfterAMonth": "einem Monat.", "deleteChatAfterAMonth": "einem Monat.",
"deleteChatAfterAYear": "einem Jahr.", "deleteChatAfterAYear": "einem Jahr.",
"yourTwonlyScore": "Dein twonly-Score" "yourTwonlyScore": "Dein twonly-Score",
"registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal."
} }

View file

@ -595,5 +595,6 @@
"deleteChatAfterAWeek": "one week.", "deleteChatAfterAWeek": "one week.",
"deleteChatAfterAMonth": "one month.", "deleteChatAfterAMonth": "one month.",
"deleteChatAfterAYear": "one year.", "deleteChatAfterAYear": "one year.",
"yourTwonlyScore": "Your twonly-Score" "yourTwonlyScore": "Your twonly-Score",
"registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days."
} }

View file

@ -2677,6 +2677,12 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Your twonly-Score'** /// **'Your twonly-Score'**
String get yourTwonlyScore; String get yourTwonlyScore;
/// No description provided for @registrationClosed.
///
/// In en, this message translates to:
/// **'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'**
String get registrationClosed;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View file

@ -1477,4 +1477,8 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get yourTwonlyScore => 'Dein twonly-Score'; String get yourTwonlyScore => 'Dein twonly-Score';
@override
String get registrationClosed =>
'Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.';
} }

View file

@ -1467,4 +1467,8 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get yourTwonlyScore => 'Your twonly-Score'; String get yourTwonlyScore => 'Your twonly-Score';
@override
String get registrationClosed =>
'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.';
} }

View file

@ -51,8 +51,9 @@ final lockRetransStore = Mutex();
/// errors or network changes. /// errors or network changes.
class ApiService { class ApiService {
ApiService(); ApiService();
final String apiHost = kReleaseMode ? 'api.twonly.eu' : '10.99.0.140:3030'; // final String apiHost = kReleaseMode ? 'api.twonly.eu' : '10.99.0.140:3030';
final String apiSecure = kReleaseMode ? 's' : ''; final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu';
final String apiSecure = kReleaseMode ? 's' : 's';
bool appIsOutdated = false; bool appIsOutdated = false;
bool isAuthenticated = false; bool isAuthenticated = false;
@ -508,15 +509,19 @@ class ApiService {
return null; return null;
} }
Future<Response_ProofOfWork?> getProofOfWork() async { Future<(Response_ProofOfWork?, bool)> getProofOfWork() async {
final handshake = Handshake()..requestPOW = Handshake_RequestPOW(); final handshake = Handshake()..requestPOW = Handshake_RequestPOW();
final req = createClientToServerFromHandshake(handshake); final req = createClientToServerFromHandshake(handshake);
final result = await sendRequestSync(req, authenticated: false); final result = await sendRequestSync(req, authenticated: false);
if (result.isError) { if (result.isError) {
Log.error('could not request proof of work params', result); Log.error('could not request proof of work params', result);
return null; if (result.error == ErrorCode.RegistrationDisabled) {
return (null, true);
}
Log.error('could not request proof of work params', result);
return (null, false);
} }
return result.value.proofOfWork as Response_ProofOfWork; return (result.value.proofOfWork as Response_ProofOfWork, false);
} }
Future<Result> downloadDone(List<int> token) async { Future<Result> downloadDone(List<int> token) async {

View file

@ -24,8 +24,24 @@ Future<void> finishStartedPreprocessing() async {
await twonlyDB.mediaFilesDao.getAllMediaFilesPendingUpload(); await twonlyDB.mediaFilesDao.getAllMediaFilesPendingUpload();
for (final mediaFile in mediaFiles) { for (final mediaFile in mediaFiles) {
if (mediaFile.isDraftMedia) {
continue;
}
try { try {
final service = await MediaFileService.fromMedia(mediaFile); final service = await MediaFileService.fromMedia(mediaFile);
if (!service.originalPath.existsSync() &&
!service.uploadRequestPath.existsSync()) {
if (service.storedPath.existsSync()) {
// media files was just stored..
continue;
}
Log.info(
'Deleted media files, as originalPath and uploadRequestPath both do not exists',
);
// the file does not exists anymore.
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId);
continue;
}
await startBackgroundMediaUpload(service); await startBackgroundMediaUpload(service);
} catch (e) { } catch (e) {
Log.error(e); Log.error(e);
@ -35,18 +51,24 @@ Future<void> finishStartedPreprocessing() async {
Future<MediaFileService?> initializeMediaUpload( Future<MediaFileService?> initializeMediaUpload(
MediaType type, MediaType type,
int? displayLimitInMilliseconds, int? displayLimitInMilliseconds, {
) async { bool isDraftMedia = false,
}) async {
final chacha20 = FlutterChacha20.poly1305Aead(); final chacha20 = FlutterChacha20.poly1305Aead();
final encryptionKey = await (await chacha20.newSecretKey()).extract(); final encryptionKey = await (await chacha20.newSecretKey()).extract();
final encryptionNonce = chacha20.newNonce(); final encryptionNonce = chacha20.newNonce();
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
const MediaFilesCompanion(isDraftMedia: Value(false)),
);
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
MediaFilesCompanion( MediaFilesCompanion(
uploadState: const Value(UploadState.initialized), uploadState: const Value(UploadState.initialized),
displayLimitInMilliseconds: Value(displayLimitInMilliseconds), displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
encryptionKey: Value(Uint8List.fromList(encryptionKey.bytes)), encryptionKey: Value(Uint8List.fromList(encryptionKey.bytes)),
encryptionNonce: Value(Uint8List.fromList(encryptionNonce)), encryptionNonce: Value(Uint8List.fromList(encryptionNonce)),
isDraftMedia: Value(isDraftMedia),
type: Value(type), type: Value(type),
), ),
); );
@ -58,6 +80,11 @@ Future<void> insertMediaFileInMessagesTable(
MediaFileService mediaService, MediaFileService mediaService,
List<String> groupIds, List<String> groupIds,
) async { ) async {
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
const MediaFilesCompanion(
isDraftMedia: Value(false),
),
);
for (final groupId in groupIds) { for (final groupId in groupIds) {
final message = await twonlyDB.messagesDao.insertMessage( final message = await twonlyDB.messagesDao.insertMessage(
MessagesCompanion( MessagesCompanion(

View file

@ -65,20 +65,24 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
if (media.tempPath.existsSync()) { if (media.tempPath.existsSync()) {
media.tempPath.deleteSync(); media.tempPath.deleteSync();
} }
if (media.ffmpegOutputPath.existsSync()) {
media.ffmpegOutputPath.deleteSync();
}
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
var command = var command =
'-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -map "0:a?" -preset veryfast -crf 28 -c:a aac -b:a 64k "${media.tempPath.path}"'; '-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -map "0:a?" -preset veryfast -crf 28 -c:a aac -b:a 64k "${media.ffmpegOutputPath.path}"';
if (media.removeAudio) { if (media.removeAudio) {
command = command =
'-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -preset veryfast -crf 28 -an "${media.tempPath.path}"'; '-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -preset veryfast -crf 28 -an "${media.ffmpegOutputPath.path}"';
} }
final session = await FFmpegKit.execute(command); final session = await FFmpegKit.execute(command);
final returnCode = await session.getReturnCode(); final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) { if (ReturnCode.isSuccess(returnCode)) {
media.ffmpegOutputPath.copySync(media.tempPath.path);
stopwatch.stop(); stopwatch.stop();
Log.info( Log.info(
'It took ${stopwatch.elapsedMilliseconds}ms to compress the video', 'It took ${stopwatch.elapsedMilliseconds}ms to compress the video',

View file

@ -45,11 +45,16 @@ class MediaFileService {
var delete = true; var delete = true;
final service = await MediaFileService.fromMediaId(mediaId); final service = await MediaFileService.fromMediaId(mediaId);
if (service == null) { if (service == null) {
Log.error( Log.error(
'Purging media file, as it is not in the database $mediaId.', 'Purging media file, as it is not in the database $mediaId.',
); );
} else { } else {
if (service.mediaFile.isDraftMedia) {
delete = false;
}
final messages = final messages =
await twonlyDB.messagesDao.getMessagesByMediaId(mediaId); await twonlyDB.messagesDao.getMessagesByMediaId(mediaId);
@ -302,6 +307,10 @@ class MediaFileService {
'tmp', 'tmp',
namePrefix: '.original', namePrefix: '.original',
); );
File get ffmpegOutputPath => _buildFilePath(
'tmp',
namePrefix: '.ffmpeg',
);
File get overlayImagePath => _buildFilePath( File get overlayImagePath => _buildFilePath(
'tmp', 'tmp',
namePrefix: '.overlay', namePrefix: '.overlay',

View file

@ -253,7 +253,7 @@ String getPushNotificationText(PushNotification pushNotification) {
PushKind.twonly.name: lang.notificationTwonly(inGroup), PushKind.twonly.name: lang.notificationTwonly(inGroup),
PushKind.video.name: lang.notificationVideo(inGroup), PushKind.video.name: lang.notificationVideo(inGroup),
PushKind.image.name: lang.notificationImage(inGroup), PushKind.image.name: lang.notificationImage(inGroup),
PushKind.video.name: lang.notificationAudio(inGroup), PushKind.audio.name: lang.notificationAudio(inGroup),
PushKind.contactRequest.name: lang.notificationContactRequest, PushKind.contactRequest.name: lang.notificationContactRequest,
PushKind.acceptRequest.name: lang.notificationAcceptRequest, PushKind.acceptRequest.name: lang.notificationAcceptRequest,
PushKind.storedMediaFile.name: lang.notificationStoredMediaFile, PushKind.storedMediaFile.name: lang.notificationStoredMediaFile,

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_android_volume_keydown/flutter_android_volume_keydown.dart'; import 'package:flutter_android_volume_keydown/flutter_android_volume_keydown.dart';
@ -352,6 +353,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
final mediaFileService = await initializeMediaUpload( final mediaFileService = await initializeMediaUpload(
type, type,
gUser.defaultShowTime, gUser.defaultShowTime,
isDraftMedia: true,
); );
if (!mounted) return true; if (!mounted) return true;

View file

@ -2,11 +2,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hashlib/random.dart'; import 'package:hashlib/random.dart';
import 'package:screenshot/screenshot.dart'; import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
@ -82,6 +84,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
} else { } else {
if (widget.mediaFileService.tempPath.existsSync()) { if (widget.mediaFileService.tempPath.existsSync()) {
loadImage(widget.mediaFileService.tempPath.readAsBytes()); loadImage(widget.mediaFileService.tempPath.readAsBytes());
} else if (widget.mediaFileService.originalPath.existsSync()) {
loadImage(widget.mediaFileService.originalPath.readAsBytes());
} }
} }
} }
@ -106,6 +110,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
isDisposed = true; isDisposed = true;
layers.clear(); layers.clear();
videoController?.dispose(); videoController?.dispose();
twonlyDB.mediaFilesDao.updateAllMediaFiles(
const MediaFilesCompanion(
isDraftMedia: Value(false),
),
);
super.dispose(); super.dispose();
} }
@ -388,6 +397,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async { Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
imageBytes = await imageBytesFuture; imageBytes = await imageBytesFuture;
// store this image so it can be used as a draft in case the app is restarted
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
await currentImage.load(imageBytes); await currentImage.load(imageBytes);
if (isDisposed) return; if (isDisposed) return;

View file

@ -476,8 +476,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
if (videoController != null) if (videoController != null)
Positioned.fill( Positioned.fill(
child: VideoPlayer(videoController!), child: VideoPlayer(videoController!),
), )
if (currentMedia != null && else if (currentMedia != null &&
currentMedia!.mediaFile.type == MediaType.image || currentMedia!.mediaFile.type == MediaType.image ||
currentMedia!.mediaFile.type == MediaType.gif) currentMedia!.mediaFile.type == MediaType.gif)
Positioned.fill( Positioned.fill(

View file

@ -41,6 +41,12 @@ class _FlameCounterWidgetState extends State<FlameCounterWidget> {
if (widget.groupId == null && widget.contactId != null) { if (widget.groupId == null && widget.contactId != null) {
final group = await twonlyDB.groupsDao.getDirectChat(widget.contactId!); final group = await twonlyDB.groupsDao.getDirectChat(widget.contactId!);
groupId = group?.groupId; groupId = group?.groupId;
} else if (groupId != null) {
// do not display the flame counter for groups
final group = await twonlyDB.groupsDao.getGroup(groupId);
if (!(group?.isDirectChat ?? false)) {
return;
}
} }
if (groupId != null) { if (groupId != null) {
isBestFriend = gUser.myBestFriendGroupId == groupId; isBestFriend = gUser.myBestFriendGroupId == groupId;

View file

@ -36,7 +36,8 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
_flameCounterSub = stream.listen((counter) { _flameCounterSub = stream.listen((counter) {
if (mounted) { if (mounted) {
setState(() { setState(() {
_flameCounter = counter; _flameCounter = counter -
1; // in the watchFlameCounter a one is added, so remove this here
}); });
} }
}); });
@ -73,7 +74,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
await twonlyDB.groupsDao.updateGroup( await twonlyDB.groupsDao.updateGroup(
_groupId, _groupId,
GroupsCompanion( GroupsCompanion(
flameCounter: Value(_directChat!.maxFlameCounter - 1), flameCounter: Value(_directChat!.maxFlameCounter),
lastFlameCounterChange: Value(DateTime.now()), lastFlameCounterChange: Value(DateTime.now()),
), ),
); );
@ -84,7 +85,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_directChat == null || if (_directChat == null ||
_directChat!.maxFlameCounter == 0 || _directChat!.maxFlameCounter == 0 ||
_flameCounter >= (_directChat!.maxFlameCounter + 1) || _flameCounter >= _directChat!.maxFlameCounter ||
_directChat!.maxFlameCounterFrom! _directChat!.maxFlameCounterFrom!
.isBefore(DateTime.now().subtract(const Duration(days: 4)))) { .isBefore(DateTime.now().subtract(const Duration(days: 4)))) {
return Container(); return Container();
@ -97,7 +98,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
emoji: '🔥', emoji: '🔥',
), ),
), ),
text: 'Restore your ${_directChat!.maxFlameCounter} lost flames', text: 'Restore your ${_directChat!.maxFlameCounter + 1} lost flames',
); );
} }
} }

View file

@ -4,10 +4,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:screenshot/screenshot.dart'; import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
import 'package:twonly/src/views/chats/chat_list.view.dart'; import 'package:twonly/src/views/chats/chat_list.view.dart';
import 'package:twonly/src/views/memories/memories.view.dart'; import 'package:twonly/src/views/memories/memories.view.dart';
@ -145,6 +148,21 @@ class HomeViewState extends State<HomeView> {
globalUpdateOfHomeViewPageIndex(0); globalUpdateOfHomeViewPageIndex(0);
} }
} }
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
if (draftMedia != null) {
final service = await MediaFileService.fromMedia(draftMedia);
if (!mounted) return;
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShareImageEditorView(
mediaFileService: service,
sharedFromGallery: true,
),
),
);
}
} }
@override @override

View file

@ -27,7 +27,7 @@ class RegisterView extends StatefulWidget {
}); });
final Function callbackOnSuccess; final Function callbackOnSuccess;
final Future<int>? proofOfWork; final (Future<int>?, bool) proofOfWork;
@override @override
State<RegisterView> createState() => _RegisterViewState(); State<RegisterView> createState() => _RegisterViewState();
} }
@ -36,10 +36,20 @@ class _RegisterViewState extends State<RegisterView> {
final TextEditingController usernameController = TextEditingController(); final TextEditingController usernameController = TextEditingController();
final TextEditingController inviteCodeController = TextEditingController(); final TextEditingController inviteCodeController = TextEditingController();
bool _registrationDisabled = false;
bool _isTryingToRegister = false; bool _isTryingToRegister = false;
bool _isValidUserName = false; bool _isValidUserName = false;
bool _showUserNameError = false; bool _showUserNameError = false;
late Future<int>? proofOfWork;
@override
void initState() {
proofOfWork = widget.proofOfWork.$1;
_registrationDisabled = widget.proofOfWork.$2;
super.initState();
}
Future<void> createNewUser() async { Future<void> createNewUser() async {
if (!_isValidUserName) { if (!_isValidUserName) {
setState(() { setState(() {
@ -57,11 +67,12 @@ class _RegisterViewState extends State<RegisterView> {
late int proof; late int proof;
if (widget.proofOfWork != null) { if (proofOfWork != null) {
proof = await widget.proofOfWork!; proof = await proofOfWork!;
} else { } else {
final pow = await apiService.getProofOfWork(); final (pow, registrationDisabled) = await apiService.getProofOfWork();
if (pow == null) { if (pow == null) {
_registrationDisabled = registrationDisabled;
if (mounted) { if (mounted) {
showNetworkIssue(context); showNetworkIssue(context);
} }
@ -82,6 +93,10 @@ class _RegisterViewState extends State<RegisterView> {
Log.info('Got user_id ${res.value} from server'); Log.info('Got user_id ${res.value} from server');
userId = res.value.userid.toInt() as int; userId = res.value.userid.toInt() as int;
} else { } else {
if (res.error == ErrorCode.RegistrationDisabled) {
_registrationDisabled = true;
return;
}
if (res.error == ErrorCode.UserIdAlreadyTaken) { if (res.error == ErrorCode.UserIdAlreadyTaken) {
Log.error('User ID already token. Tying again.'); Log.error('User ID already token. Tying again.');
await deleteLocalUserData(); await deleteLocalUserData();
@ -127,6 +142,43 @@ class _RegisterViewState extends State<RegisterView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_registrationDisabled) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: ListView(
children: [
const SizedBox(height: 50),
Text(
context.lang.registerTitle,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 30),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Text(
context.lang.registerSlogan,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12),
),
),
const SizedBox(height: 130),
Text(
context.lang.registrationClosed,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.red,
),
),
],
),
),
),
);
}
InputDecoration getInputDecoration(String hintText) { InputDecoration getInputDecoration(String hintText) {
return InputDecoration(hintText: hintText, fillColor: Colors.grey[400]); return InputDecoration(hintText: hintText, fillColor: Colors.grey[400]);
} }