fix: все фото в пикере

This commit is contained in:
Jganenokk
2026-08-26 21:34:17 +07:00
parent 5d115e0f9e
commit 9b37565887
14 changed files with 423 additions and 151 deletions
+9
View File
@@ -99,6 +99,15 @@ class CallBridge {
}
}
Future<void> dropOngoing() async {
if (!_android) return;
try {
await _method.invokeMethod<void>('dropOngoing');
} catch (e) {
logger.w('CallBridge.dropOngoing: $e');
}
}
Future<void> notifyEnded() async {
if (!_android) return;
try {
+44 -19
View File
@@ -57,6 +57,7 @@ class CallController {
Stream<void> get incomingCanceled => _canceled.stream;
CallSession? _active;
StreamSubscription<CallSessionState>? _activeSub;
CallSession? get activeSession => _active;
IncomingCall? _pending;
@@ -161,10 +162,7 @@ class CallController {
osVersion: _api?.callsOsVersion,
);
final session = CallSession(ws2Config: config, role: CallRole.caller);
_bind(session);
await session.start();
CallBridge.instance.notifyAccepted();
return session;
return _launch(session, session.start);
}
Future<CreatedCall> createConference() async {
@@ -189,10 +187,7 @@ class CallController {
role: CallRole.joiner,
isGroup: true,
);
_bind(session);
await session.start();
CallBridge.instance.notifyAccepted();
return session;
return _launch(session, session.start);
}
Future<CallSession> acceptIncoming(IncomingCall call) async {
@@ -209,11 +204,14 @@ class CallController {
params: call.params,
role: CallRole.callee,
);
_bind(session);
await session.start();
await session.accept();
CallBridge.instance.notifyAccepted(caller: call.callerName);
return session;
return _launch(
session,
() async {
await session.start();
await session.accept();
},
caller: call.callerName,
);
}
Future<void> rejectIncoming(IncomingCall call) async {
@@ -244,18 +242,45 @@ class CallController {
return true;
}
Future<CallSession> _launch(
CallSession session,
Future<void> Function() open, {
String? caller,
}) async {
_bind(session);
try {
await open();
} catch (_) {
await _release(session);
try {
await session.hangup();
} catch (_) {}
rethrow;
}
CallBridge.instance.notifyAccepted(caller: caller);
return session;
}
void _bind(CallSession session) {
unawaited(_activeSub?.cancel());
_active = session;
session.stateStream.listen((state) {
if (state == CallSessionState.ended && _active == session) {
_active = null;
CallBridge.instance.notifyEnded();
_ended.add(null);
}
_activeSub = session.stateStream.listen((state) {
if (state != CallSessionState.ended) return;
unawaited(_release(session));
});
}
Future<void> _release(CallSession session) async {
if (!identical(_active, session)) return;
_active = null;
await _activeSub?.cancel();
_activeSub = null;
CallBridge.instance.notifyEnded();
_ended.add(null);
}
void dispose() {
_activeSub?.cancel();
_pushSub?.cancel();
_incoming.close();
_ended.close();
+83 -32
View File
@@ -25,6 +25,15 @@ abstract class GalleryItem {
static GalleryItem fromFile(File file) => _FileGalleryItem(file);
}
class GalleryPage {
const GalleryPage({required this.items, required this.hasMore});
static const empty = GalleryPage(items: <GalleryItem>[], hasMore: false);
final List<GalleryItem> items;
final bool hasMore;
}
class PickedPhoto {
final GalleryItem item;
final File? editedFile;
@@ -49,8 +58,10 @@ Future<(int, int)?> imageFileDimensions(File file) async {
}
abstract class GallerySource {
static const int pageSize = 120;
Future<GalleryPermission> ensurePermission();
Future<List<GalleryItem>> load({int limit});
Future<GalleryPage> load({int offset, int limit});
Future<void> openSettings();
Future<void> manageAccess();
@@ -71,20 +82,51 @@ class _PhotoManagerSource implements GallerySource {
return GalleryPermission.denied;
}
@override
Future<List<GalleryItem>> load({int limit = 120}) async {
final paths = await PhotoManager.getAssetPathList(
type: RequestType.common,
onlyAll: true,
filterOption: FilterOptionGroup(
orders: const [
OrderOption(type: OrderOptionType.createDate, asc: false),
],
AssetPathEntity? _album;
int _total = 0;
static FilterOptionGroup _filter() => FilterOptionGroup(
imageOption: const FilterOption(
sizeConstraint: SizeConstraint(ignoreSize: true),
),
videoOption: const FilterOption(
sizeConstraint: SizeConstraint(ignoreSize: true),
durationConstraint: DurationConstraint(
max: Duration(days: 365),
allowNullable: true,
),
),
createTimeCond: DateTimeCond.def().copyWith(ignore: true),
orders: const [OrderOption(type: OrderOptionType.createDate, asc: false)],
);
@override
Future<GalleryPage> load({
int offset = 0,
int limit = GallerySource.pageSize,
}) async {
if (offset == 0 || _album == null) {
final paths = await PhotoManager.getAssetPathList(
type: RequestType.common,
onlyAll: true,
filterOption: _filter(),
);
if (paths.isEmpty) {
_album = null;
_total = 0;
return GalleryPage.empty;
}
_album = paths.first;
_total = await paths.first.assetCountAsync;
}
final album = _album;
if (album == null || offset >= _total) return GalleryPage.empty;
final end = offset + limit < _total ? offset + limit : _total;
final assets = await album.getAssetListRange(start: offset, end: end);
return GalleryPage(
items: assets.map((a) => _AssetGalleryItem(a)).toList(),
hasMore: end < _total,
);
if (paths.isEmpty) return const [];
final assets = await paths.first.getAssetListRange(start: 0, end: limit);
return assets.map((a) => _AssetGalleryItem(a)).toList();
}
@override
@@ -172,8 +214,34 @@ class _DesktopGallerySource implements GallerySource {
Future<GalleryPermission> ensurePermission() async =>
GalleryPermission.granted;
List<_FileGalleryItem> _all = const [];
@override
Future<List<GalleryItem>> load({int limit = 120}) async {
Future<GalleryPage> load({
int offset = 0,
int limit = GallerySource.pageSize,
}) async {
if (offset == 0 || _all.isEmpty) _all = _scan();
if (offset >= _all.length) return GalleryPage.empty;
final end = offset + limit < _all.length ? offset + limit : _all.length;
final items = _all.sublist(offset, end);
const batch = 8;
const eager = 24;
Future<void> probeRange(int from, int to) async {
for (var i = from; i < to; i += batch) {
final stop = i + batch > to ? to : i + batch;
await Future.wait(items.sublist(i, stop).map((it) => it.probe()));
}
}
final head = items.length < eager ? items.length : eager;
await probeRange(0, head);
if (head < items.length) unawaited(probeRange(head, items.length));
return GalleryPage(items: items, hasMore: end < _all.length);
}
List<_FileGalleryItem> _scan() {
final entries = <({File file, DateTime modified})>[];
for (final dir in _candidateDirs()) {
if (!dir.existsSync()) continue;
@@ -185,24 +253,7 @@ class _DesktopGallerySource implements GallerySource {
} catch (_) {}
}
entries.sort((a, b) => b.modified.compareTo(a.modified));
final items = entries
.take(limit)
.map((e) => _FileGalleryItem(e.file))
.toList();
const batch = 8;
const eager = 24;
Future<void> probeRange(int from, int to) async {
for (var i = from; i < to; i += batch) {
final end = i + batch > to ? to : i + batch;
await Future.wait(items.sublist(i, end).map((it) => it.probe()));
}
}
final head = items.length < eager ? items.length : eager;
await probeRange(0, head);
if (head < items.length) unawaited(probeRange(head, items.length));
return items;
return entries.map((e) => _FileGalleryItem(e.file)).toList();
}
@override
+41
View File
@@ -0,0 +1,41 @@
import 'dart:io' show Platform;
import 'package:flutter/services.dart';
import 'logger.dart';
class ScreenWake {
ScreenWake._();
static final ScreenWake instance = ScreenWake._();
static const _channel = MethodChannel('ru.komet.app/screen');
final Set<Object> _holders = <Object>{};
bool get _supported {
try {
return Platform.isAndroid || Platform.isIOS;
} catch (_) {
return false;
}
}
Future<void> acquire(Object holder) async {
if (!_supported || !_holders.add(holder)) return;
if (_holders.length == 1) await _apply(true);
}
Future<void> release(Object holder) async {
if (!_supported || !_holders.remove(holder)) return;
if (_holders.isEmpty) await _apply(false);
}
Future<void> _apply(bool enabled) async {
try {
await _channel.invokeMethod<void>('setKeepAwake', {'enabled': enabled});
} catch (e) {
logger.w('ScreenWake._apply: enabled=$enabled $e');
}
}
}