Compare commits
9 Commits
f4e2c0440d
...
28fda3d0c7
| Author | SHA1 | Date | |
|---|---|---|---|
|
28fda3d0c7
|
|||
|
187c2ea43e
|
|||
|
ae7d967461
|
|||
|
1ce71f1fa1
|
|||
|
9b68808c77
|
|||
|
|
99b7bf8199 | ||
|
|
eb9bb73c31 | ||
|
|
a8c3830d67 | ||
|
|
07a5a19141 |
@@ -349,6 +349,8 @@
|
|||||||
"chatBreakNone": "None",
|
"chatBreakNone": "None",
|
||||||
"settingsRealmCompactView": "Compact Realm View",
|
"settingsRealmCompactView": "Compact Realm View",
|
||||||
"settingsMixedFeed": "Mixed Feed",
|
"settingsMixedFeed": "Mixed Feed",
|
||||||
|
"settingsDataSavingMode": "Data Saving Mode",
|
||||||
|
"dataSavingHint": "Data Saving Mode",
|
||||||
"settingsAutoTranslate": "Auto Translate",
|
"settingsAutoTranslate": "Auto Translate",
|
||||||
"settingsHideBottomNav": "Hide Bottom Navigation",
|
"settingsHideBottomNav": "Hide Bottom Navigation",
|
||||||
"settingsSoundEffects": "Sound Effects",
|
"settingsSoundEffects": "Sound Effects",
|
||||||
|
|||||||
@@ -315,6 +315,8 @@
|
|||||||
"chatBreakNone": "无",
|
"chatBreakNone": "无",
|
||||||
"settingsRealmCompactView": "紧凑领域视图",
|
"settingsRealmCompactView": "紧凑领域视图",
|
||||||
"settingsMixedFeed": "混合动态",
|
"settingsMixedFeed": "混合动态",
|
||||||
|
"settingsDataSavingMode": "流量节省模式",
|
||||||
|
"dataSavingHint": "流量节省模式",
|
||||||
"settingsAutoTranslate": "自动翻译",
|
"settingsAutoTranslate": "自动翻译",
|
||||||
"settingsHideBottomNav": "隐藏底部导航",
|
"settingsHideBottomNav": "隐藏底部导航",
|
||||||
"settingsSoundEffects": "音效",
|
"settingsSoundEffects": "音效",
|
||||||
|
|||||||
@@ -315,6 +315,8 @@
|
|||||||
"settingsRealmCompactView": "緊湊領域視圖",
|
"settingsRealmCompactView": "緊湊領域視圖",
|
||||||
"settingsMixedFeed": "混合動態",
|
"settingsMixedFeed": "混合動態",
|
||||||
"settingsAutoTranslate": "自動翻譯",
|
"settingsAutoTranslate": "自動翻譯",
|
||||||
|
"settingsDataSavingMode": "低數據模式",
|
||||||
|
"dataSavingHint": "低數據模式",
|
||||||
"settingsHideBottomNav": "隱藏底部導航",
|
"settingsHideBottomNav": "隱藏底部導航",
|
||||||
"settingsSoundEffects": "音效",
|
"settingsSoundEffects": "音效",
|
||||||
"settingsAprilFoolFeatures": "愚人節功能",
|
"settingsAprilFoolFeatures": "愚人節功能",
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
|
private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
|
||||||
switch content.userInfo["type"] as? String {
|
switch content.userInfo["type"] as? String {
|
||||||
case "messages.new":
|
case "messages.new":
|
||||||
|
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
||||||
try handleMessagingNotification(request: request, content: content)
|
try handleMessagingNotification(request: request, content: content)
|
||||||
default:
|
default:
|
||||||
try handleDefaultNotification(content: content)
|
try handleDefaultNotification(content: content)
|
||||||
@@ -60,8 +61,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
let pfpIdentifier = meta["pfp"] as? String
|
let pfpIdentifier = meta["pfp"] as? String
|
||||||
|
|
||||||
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
|
||||||
|
|
||||||
let metaCopy = meta as? [String: Any] ?? [:]
|
let metaCopy = meta as? [String: Any] ?? [:]
|
||||||
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
|
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const kAppColorSchemeStoreKey = 'app_color_scheme';
|
|||||||
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
||||||
const kAppCustomFonts = 'app_custom_fonts';
|
const kAppCustomFonts = 'app_custom_fonts';
|
||||||
const kAppAutoTranslate = 'app_auto_translate';
|
const kAppAutoTranslate = 'app_auto_translate';
|
||||||
|
const kAppDataSavingMode = 'app_data_saving_mode';
|
||||||
const kAppSoundEffects = 'app_sound_effects';
|
const kAppSoundEffects = 'app_sound_effects';
|
||||||
const kAppAprilFoolFeatures = 'app_april_fool_features';
|
const kAppAprilFoolFeatures = 'app_april_fool_features';
|
||||||
const kAppWindowSize = 'app_window_size';
|
const kAppWindowSize = 'app_window_size';
|
||||||
@@ -55,6 +56,7 @@ final serverUrlProvider = Provider<String>((ref) {
|
|||||||
sealed class AppSettings with _$AppSettings {
|
sealed class AppSettings with _$AppSettings {
|
||||||
const factory AppSettings({
|
const factory AppSettings({
|
||||||
required bool autoTranslate,
|
required bool autoTranslate,
|
||||||
|
required bool dataSavingMode,
|
||||||
required bool soundEffects,
|
required bool soundEffects,
|
||||||
required bool aprilFoolFeatures,
|
required bool aprilFoolFeatures,
|
||||||
required bool enterToSend,
|
required bool enterToSend,
|
||||||
@@ -73,6 +75,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
final prefs = ref.watch(sharedPreferencesProvider);
|
final prefs = ref.watch(sharedPreferencesProvider);
|
||||||
return AppSettings(
|
return AppSettings(
|
||||||
autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false,
|
autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false,
|
||||||
|
dataSavingMode: prefs.getBool(kAppDataSavingMode) ?? false,
|
||||||
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
|
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
|
||||||
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
|
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
|
||||||
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
|
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
|
||||||
@@ -107,6 +110,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
state = state.copyWith(autoTranslate: value);
|
state = state.copyWith(autoTranslate: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDataSavingMode(bool value){
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
prefs.setBool(kAppDataSavingMode, value);
|
||||||
|
state = state.copyWith(dataSavingMode: value);
|
||||||
|
}
|
||||||
|
|
||||||
void setSoundEffects(bool value) {
|
void setSoundEffects(bool value) {
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
prefs.setBool(kAppSoundEffects, value);
|
prefs.setBool(kAppSoundEffects, value);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$AppSettings {
|
mixin _$AppSettings {
|
||||||
|
|
||||||
bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type
|
bool get autoTranslate; bool get dataSavingMode; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type
|
||||||
Size? get windowSize;
|
Size? get windowSize;
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -26,16 +26,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize);
|
int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> {
|
|||||||
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
|
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize
|
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -63,9 +63,10 @@ class _$AppSettingsCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
|
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
|
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
|
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -156,10 +157,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _AppSettings() when $default != null:
|
case _AppSettings() when $default != null:
|
||||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -177,10 +178,10 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _AppSettings():
|
case _AppSettings():
|
||||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);}
|
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -194,10 +195,10 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _AppSettings() when $default != null:
|
case _AppSettings() when $default != null:
|
||||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -209,10 +210,11 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_
|
|||||||
|
|
||||||
|
|
||||||
class _AppSettings implements AppSettings {
|
class _AppSettings implements AppSettings {
|
||||||
const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize});
|
const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize});
|
||||||
|
|
||||||
|
|
||||||
@override final bool autoTranslate;
|
@override final bool autoTranslate;
|
||||||
|
@override final bool dataSavingMode;
|
||||||
@override final bool soundEffects;
|
@override final bool soundEffects;
|
||||||
@override final bool aprilFoolFeatures;
|
@override final bool aprilFoolFeatures;
|
||||||
@override final bool enterToSend;
|
@override final bool enterToSend;
|
||||||
@@ -233,16 +235,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize);
|
int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -253,7 +255,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith
|
|||||||
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
|
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize
|
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -270,9 +272,10 @@ class __$AppSettingsCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
||||||
return _then(_AppSettings(
|
return _then(_AppSettings(
|
||||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
|
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
|
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
|
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'config.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$appSettingsNotifierHash() =>
|
String _$appSettingsNotifierHash() =>
|
||||||
r'e3c13307eabb0201487b85ab67b1ab493e588e71';
|
r'cd18bff2614a94e3523634e6c577cefad0367eba';
|
||||||
|
|
||||||
/// See also [AppSettingsNotifier].
|
/// See also [AppSettingsNotifier].
|
||||||
@ProviderFor(AppSettingsNotifier)
|
@ProviderFor(AppSettingsNotifier)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/models/developer.dart';
|
import 'package:island/models/developer.dart';
|
||||||
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/models/relationship.dart';
|
import 'package:island/models/relationship.dart';
|
||||||
import 'package:island/models/account.dart';
|
import 'package:island/models/account.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
@@ -39,6 +40,465 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||||||
|
|
||||||
part 'profile.g.dart';
|
part 'profile.g.dart';
|
||||||
|
|
||||||
|
class _AccountBasicInfo extends StatelessWidget {
|
||||||
|
final SnAccount data;
|
||||||
|
final String uname;
|
||||||
|
final AsyncValue<SnDeveloper?> accountDeveloper;
|
||||||
|
|
||||||
|
const _AccountBasicInfo({
|
||||||
|
required this.data,
|
||||||
|
required this.uname,
|
||||||
|
required this.accountDeveloper,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ProfilePictureWidget(file: data.profile.picture, radius: 32),
|
||||||
|
const Gap(20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
AccountName(account: data, style: TextStyle(fontSize: 20)),
|
||||||
|
const Gap(6),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'@${data.name}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).fontSize(14).opacity(0.85),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (accountDeveloper.value != null)
|
||||||
|
Row(
|
||||||
|
spacing: 7,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.smart_toy, size: 18),
|
||||||
|
Text(
|
||||||
|
'botAutomatedBy'.tr(
|
||||||
|
args: [accountDeveloper.value!.publisher!.nick],
|
||||||
|
),
|
||||||
|
).fontSize(13),
|
||||||
|
],
|
||||||
|
).opacity(0.75),
|
||||||
|
const Gap(4),
|
||||||
|
AccountStatusWidget(uname: uname, padding: EdgeInsets.zero),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
SharePlus.instance.share(
|
||||||
|
ShareParams(
|
||||||
|
uri: Uri.parse('https://id.solian.app/@${data.name}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.share),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountProfileBio extends StatelessWidget {
|
||||||
|
final SnAccount data;
|
||||||
|
|
||||||
|
const _AccountProfileBio({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||||
|
if (data.profile.bio.isEmpty)
|
||||||
|
Text('descriptionNone').tr().italic()
|
||||||
|
else
|
||||||
|
MarkdownTextContent(
|
||||||
|
content: data.profile.bio,
|
||||||
|
linesMargin: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 20),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountProfileDetail extends StatelessWidget {
|
||||||
|
final SnAccount data;
|
||||||
|
|
||||||
|
const _AccountProfileDetail({required this.data});
|
||||||
|
|
||||||
|
List<Widget> _buildSubcolumn() {
|
||||||
|
return [
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.join, size: 17, fill: 1),
|
||||||
|
Text(
|
||||||
|
'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.profile.birthday != null)
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.cake, size: 17, fill: 1),
|
||||||
|
Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')),
|
||||||
|
Text('·').bold(),
|
||||||
|
Text(
|
||||||
|
'${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.profile.location.isNotEmpty)
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.location_on, size: 17, fill: 1),
|
||||||
|
Text(data.profile.location),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty)
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.person, size: 17, fill: 1),
|
||||||
|
Text(
|
||||||
|
data.profile.gender.isEmpty
|
||||||
|
? 'unspecified'.tr()
|
||||||
|
: data.profile.gender,
|
||||||
|
),
|
||||||
|
Text('·').bold(),
|
||||||
|
Text(
|
||||||
|
data.profile.pronouns.isEmpty
|
||||||
|
? 'unspecified'.tr()
|
||||||
|
: data.profile.pronouns,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.profile.firstName.isNotEmpty ||
|
||||||
|
data.profile.middleName.isNotEmpty ||
|
||||||
|
data.profile.lastName.isNotEmpty)
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.id_card, size: 17, fill: 1),
|
||||||
|
if (data.profile.firstName.isNotEmpty) Text(data.profile.firstName),
|
||||||
|
if (data.profile.middleName.isNotEmpty)
|
||||||
|
Text(data.profile.middleName),
|
||||||
|
if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: 'creditsStatus'.tr(),
|
||||||
|
child: Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2),
|
||||||
|
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
|
||||||
|
Text('·').bold(),
|
||||||
|
switch (data.profile.socialCreditsLevel) {
|
||||||
|
-1 => Text('socialCreditsLevelPoor').tr(),
|
||||||
|
0 => Text('socialCreditsLevelNormal').tr(),
|
||||||
|
1 => Text('socialCreditsLevelGood').tr(),
|
||||||
|
2 => Text('socialCreditsLevelExcellent').tr(),
|
||||||
|
_ => Text('unknown').tr(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
data.id,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: data.id));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
if (_buildSubcolumn().isNotEmpty)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 2,
|
||||||
|
children: _buildSubcolumn(),
|
||||||
|
),
|
||||||
|
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('timeZone').tr().bold(),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Text(data.profile.timeZone),
|
||||||
|
Text(
|
||||||
|
getTzInfo(
|
||||||
|
data.profile.timeZone,
|
||||||
|
).$2.formatCustomGlobal('HH:mm'),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
|
||||||
|
).fontSize(11),
|
||||||
|
Text(
|
||||||
|
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
||||||
|
).fontSize(11).opacity(0.75),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 16),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountProfileLinks extends StatelessWidget {
|
||||||
|
final SnAccount data;
|
||||||
|
|
||||||
|
const _AccountProfileLinks({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||||
|
for (final link in data.profile.links)
|
||||||
|
ListTile(
|
||||||
|
title: Text(link.name.capitalizeEachWord()),
|
||||||
|
subtitle: Text(link.url),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (!link.url.startsWith('http') && !link.url.contains('://')) {
|
||||||
|
launchUrlString('https://${link.url}');
|
||||||
|
} else {
|
||||||
|
launchUrlString(link.url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountPublisherList extends StatelessWidget {
|
||||||
|
final List<SnPublisher> publishers;
|
||||||
|
|
||||||
|
const _AccountPublisherList({required this.publishers});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (publishers.isEmpty) return const SizedBox.shrink();
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'publishers',
|
||||||
|
).tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||||
|
for (final publisher in publishers)
|
||||||
|
ListTile(
|
||||||
|
title: Text(publisher.nick),
|
||||||
|
subtitle:
|
||||||
|
publisher.bio.isNotEmpty
|
||||||
|
? Text(
|
||||||
|
publisher.bio,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
file: publisher.picture,
|
||||||
|
borderRadius: publisher.type == 1 ? 8 : null,
|
||||||
|
),
|
||||||
|
isThreeLine: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
context.pushNamed(
|
||||||
|
'publisherProfile',
|
||||||
|
pathParameters: {'name': publisher.name},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountAction extends StatelessWidget {
|
||||||
|
final SnAccount data;
|
||||||
|
final AsyncValue<SnRelationship?> accountRelationship;
|
||||||
|
final AsyncValue<SnChatRoom?> accountChat;
|
||||||
|
final VoidCallback relationshipAction;
|
||||||
|
final VoidCallback blockAction;
|
||||||
|
final VoidCallback directMessageAction;
|
||||||
|
|
||||||
|
const _AccountAction({
|
||||||
|
required this.data,
|
||||||
|
required this.accountRelationship,
|
||||||
|
required this.accountChat,
|
||||||
|
required this.relationshipAction,
|
||||||
|
required this.blockAction,
|
||||||
|
required this.directMessageAction,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
if (accountRelationship.value == null ||
|
||||||
|
accountRelationship.value!.status > -100)
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: relationshipAction,
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? 'addFriendShort'
|
||||||
|
: 'added',
|
||||||
|
).tr(),
|
||||||
|
icon:
|
||||||
|
accountRelationship.value == null
|
||||||
|
? const Icon(Symbols.person_add)
|
||||||
|
: const Icon(Symbols.person_check),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (accountRelationship.value == null ||
|
||||||
|
accountRelationship.value!.status <= -100)
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: blockAction,
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? 'blockUser'
|
||||||
|
: 'unblockUser',
|
||||||
|
).tr(),
|
||||||
|
icon:
|
||||||
|
accountRelationship.value == null
|
||||||
|
? const Icon(Symbols.block)
|
||||||
|
: const Icon(Symbols.person_cancel),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: directMessageAction,
|
||||||
|
icon: const Icon(Symbols.message),
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountChat.value == null
|
||||||
|
? 'createDirectMessage'
|
||||||
|
: 'gotoDirectMessage',
|
||||||
|
maxLines: 1,
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton.filled(
|
||||||
|
onPressed: () {
|
||||||
|
showAbuseReportSheet(
|
||||||
|
context,
|
||||||
|
resourceIdentifier: 'account/${data.id}',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.flag,
|
||||||
|
color: Theme.of(context).colorScheme.onError,
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 12),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnAccount> account(Ref ref, String uname) async {
|
Future<SnAccount> account(Ref ref, String uname) async {
|
||||||
if (uname == 'me') {
|
if (uname == 'me') {
|
||||||
@@ -132,6 +592,20 @@ Future<SnDeveloper?> accountBotDeveloper(Ref ref, String uname) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnPublisher>> accountPublishers(Ref ref, String id) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
try {
|
||||||
|
final resp = await apiClient.get('/sphere/publishers/of/$id');
|
||||||
|
return resp.data
|
||||||
|
.map((e) => SnPublisher.fromJson(e))
|
||||||
|
.cast<SnPublisher>()
|
||||||
|
.toList();
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AccountProfileScreen extends HookConsumerWidget {
|
class AccountProfileScreen extends HookConsumerWidget {
|
||||||
final String name;
|
final String name;
|
||||||
const AccountProfileScreen({super.key, required this.name});
|
const AccountProfileScreen({super.key, required this.name});
|
||||||
@@ -217,354 +691,16 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> buildSubcolumn(SnAccount data) {
|
|
||||||
return [
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.join, size: 17, fill: 1),
|
|
||||||
Text(
|
|
||||||
'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (data.profile.birthday != null)
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.cake, size: 17, fill: 1),
|
|
||||||
Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')),
|
|
||||||
Text('·').bold(),
|
|
||||||
Text(
|
|
||||||
'${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (data.profile.location.isNotEmpty)
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.location_on, size: 17, fill: 1),
|
|
||||||
Text(data.profile.location),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty)
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.person, size: 17, fill: 1),
|
|
||||||
Text(
|
|
||||||
data.profile.gender.isEmpty
|
|
||||||
? 'unspecified'.tr()
|
|
||||||
: data.profile.gender,
|
|
||||||
),
|
|
||||||
Text('·').bold(),
|
|
||||||
Text(
|
|
||||||
data.profile.pronouns.isEmpty
|
|
||||||
? 'unspecified'.tr()
|
|
||||||
: data.profile.pronouns,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (data.profile.firstName.isNotEmpty ||
|
|
||||||
data.profile.middleName.isNotEmpty ||
|
|
||||||
data.profile.lastName.isNotEmpty)
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.id_card, size: 17, fill: 1),
|
|
||||||
if (data.profile.firstName.isNotEmpty)
|
|
||||||
Text(data.profile.firstName),
|
|
||||||
if (data.profile.middleName.isNotEmpty)
|
|
||||||
Text(data.profile.middleName),
|
|
||||||
if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Tooltip(
|
|
||||||
message: 'creditsStatus'.tr(),
|
|
||||||
child: Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2),
|
|
||||||
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
|
|
||||||
Text('·').bold(),
|
|
||||||
switch (data.profile.socialCreditsLevel) {
|
|
||||||
-1 => Text('socialCreditsLevelPoor').tr(),
|
|
||||||
0 => Text('socialCreditsLevelNormal').tr(),
|
|
||||||
1 => Text('socialCreditsLevelGood').tr(),
|
|
||||||
2 => Text('socialCreditsLevelExcellent').tr(),
|
|
||||||
_ => Text('unknown').tr(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2),
|
|
||||||
Text(data.id),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Clipboard.setData(ClipboardData(text: data.id));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final isCurrentUser = useMemoized(
|
final isCurrentUser = useMemoized(
|
||||||
() => user.value?.id == account.value?.id,
|
() => user.value?.id == account.value?.id,
|
||||||
[user, account],
|
[user, account],
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget accountBasicInfo(SnAccount data) => Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ProfilePictureWidget(file: data.profile.picture, radius: 32),
|
|
||||||
const Gap(20),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
AccountName(account: data, style: TextStyle(fontSize: 20)),
|
|
||||||
const Gap(6),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
'@${data.name}',
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
).fontSize(14).opacity(0.85),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (accountDeveloper.value != null)
|
|
||||||
Row(
|
|
||||||
spacing: 7,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.smart_toy, size: 18),
|
|
||||||
Text(
|
|
||||||
'botAutomatedBy'.tr(
|
|
||||||
args: [accountDeveloper.value!.publisher!.nick],
|
|
||||||
),
|
|
||||||
).fontSize(13),
|
|
||||||
],
|
|
||||||
).opacity(0.75),
|
|
||||||
const Gap(4),
|
|
||||||
AccountStatusWidget(uname: name, padding: EdgeInsets.zero),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
SharePlus.instance.share(
|
|
||||||
ShareParams(
|
|
||||||
uri: Uri.parse('https://id.solian.app/@${data.name}'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Symbols.share),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget accountProfileBio(SnAccount data) => Card(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
|
||||||
if (data.profile.bio.isEmpty)
|
|
||||||
Text('descriptionNone').tr().italic()
|
|
||||||
else
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: data.profile.bio,
|
|
||||||
linesMargin: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24, vertical: 20),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget accountProfileDetail(SnAccount data) => Card(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
spacing: 24,
|
|
||||||
children: [
|
|
||||||
if (buildSubcolumn(data).isNotEmpty)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
spacing: 2,
|
|
||||||
children: buildSubcolumn(data),
|
|
||||||
),
|
|
||||||
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('timeZone').tr().bold(),
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Text(data.profile.timeZone),
|
|
||||||
Text(
|
|
||||||
getTzInfo(
|
|
||||||
data.profile.timeZone,
|
|
||||||
).$2.formatCustomGlobal('HH:mm'),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
|
|
||||||
).fontSize(11),
|
|
||||||
Text(
|
|
||||||
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
|
||||||
).fontSize(11).opacity(0.75),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24, vertical: 16),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget accountProfileLinks(SnAccount data) => Card(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
|
||||||
for (final link in data.profile.links)
|
|
||||||
ListTile(
|
|
||||||
title: Text(link.name.capitalizeEachWord()),
|
|
||||||
subtitle: Text(link.url),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (!link.url.startsWith('http') && !link.url.contains('://')) {
|
|
||||||
launchUrlString('https://${link.url}');
|
|
||||||
} else {
|
|
||||||
launchUrlString(link.url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget accountAction(SnAccount data) => Card(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
if (accountRelationship.value == null ||
|
|
||||||
accountRelationship.value!.status > -100)
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton.icon(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? null
|
|
||||||
: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
foregroundColor: WidgetStatePropertyAll(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? null
|
|
||||||
: Theme.of(context).colorScheme.onSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: relationshipAction,
|
|
||||||
label:
|
|
||||||
Text(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? 'addFriendShort'
|
|
||||||
: 'added',
|
|
||||||
).tr(),
|
|
||||||
icon:
|
|
||||||
accountRelationship.value == null
|
|
||||||
? const Icon(Symbols.person_add)
|
|
||||||
: const Icon(Symbols.person_check),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (accountRelationship.value == null ||
|
|
||||||
accountRelationship.value!.status <= -100)
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton.icon(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? null
|
|
||||||
: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
foregroundColor: WidgetStatePropertyAll(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? null
|
|
||||||
: Theme.of(context).colorScheme.onSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: blockAction,
|
|
||||||
label:
|
|
||||||
Text(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? 'blockUser'
|
|
||||||
: 'unblockUser',
|
|
||||||
).tr(),
|
|
||||||
icon:
|
|
||||||
accountRelationship.value == null
|
|
||||||
? const Icon(Symbols.block)
|
|
||||||
: const Icon(Symbols.person_cancel),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton.icon(
|
|
||||||
onPressed: directMessageAction,
|
|
||||||
icon: const Icon(Symbols.message),
|
|
||||||
label:
|
|
||||||
Text(
|
|
||||||
accountChat.value == null
|
|
||||||
? 'createDirectMessage'
|
|
||||||
: 'gotoDirectMessage',
|
|
||||||
maxLines: 1,
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton.filled(
|
|
||||||
onPressed: () {
|
|
||||||
showAbuseReportSheet(
|
|
||||||
context,
|
|
||||||
resourceIdentifier: 'account/${data.id}',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Symbols.flag,
|
|
||||||
color: Theme.of(context).colorScheme.onError,
|
|
||||||
),
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
|
||||||
Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 16, vertical: 12),
|
|
||||||
);
|
|
||||||
|
|
||||||
return account.when(
|
return account.when(
|
||||||
data:
|
data: (data) {
|
||||||
(data) => AppScaffold(
|
final accountPublishers = ref.watch(accountPublishersProvider(data.id));
|
||||||
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar:
|
appBar:
|
||||||
isWideScreen(context)
|
isWideScreen(context)
|
||||||
@@ -595,9 +731,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color:
|
||||||
appbarColor.value ??
|
appbarColor.value ??
|
||||||
Theme.of(
|
Theme.of(context).appBarTheme.foregroundColor,
|
||||||
context,
|
|
||||||
).appBarTheme.foregroundColor,
|
|
||||||
shadows: [appbarShadow],
|
shadows: [appbarShadow],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -613,7 +747,13 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
SliverToBoxAdapter(
|
||||||
|
child: _AccountBasicInfo(
|
||||||
|
data: data,
|
||||||
|
uname: name,
|
||||||
|
accountDeveloper: accountDeveloper,
|
||||||
|
),
|
||||||
|
),
|
||||||
if (data.badges.isNotEmpty)
|
if (data.badges.isNotEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -642,14 +782,16 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
).padding(horizontal: 4, top: 8),
|
).padding(horizontal: 4, top: 8),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileBio(data).padding(top: 4),
|
child: _AccountProfileBio(
|
||||||
|
data: data,
|
||||||
|
).padding(top: 4),
|
||||||
),
|
),
|
||||||
if (data.profile.links.isNotEmpty)
|
if (data.profile.links.isNotEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileLinks(data),
|
child: _AccountProfileLinks(data: data),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileDetail(data),
|
child: _AccountProfileDetail(data: data),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -658,8 +800,22 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverGap(24),
|
SliverGap(24),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _AccountPublisherList(
|
||||||
|
publishers: accountPublishers.value ?? [],
|
||||||
|
),
|
||||||
|
),
|
||||||
if (user.value != null && !isCurrentUser)
|
if (user.value != null && !isCurrentUser)
|
||||||
SliverToBoxAdapter(child: accountAction(data)),
|
SliverToBoxAdapter(
|
||||||
|
child: _AccountAction(
|
||||||
|
data: data,
|
||||||
|
accountRelationship: accountRelationship,
|
||||||
|
accountChat: accountChat,
|
||||||
|
relationshipAction: relationshipAction,
|
||||||
|
blockAction: blockAction,
|
||||||
|
directMessageAction: directMessageAction,
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Card(
|
child: Card(
|
||||||
child: FortuneGraphWidget(
|
child: FortuneGraphWidget(
|
||||||
@@ -715,7 +871,13 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
SliverToBoxAdapter(
|
||||||
|
child: _AccountBasicInfo(
|
||||||
|
data: data,
|
||||||
|
uname: name,
|
||||||
|
accountDeveloper: accountDeveloper,
|
||||||
|
),
|
||||||
|
),
|
||||||
if (data.badges.isNotEmpty)
|
if (data.badges.isNotEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -742,22 +904,36 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileBio(data).padding(horizontal: 4),
|
child: _AccountProfileBio(
|
||||||
|
data: data,
|
||||||
|
).padding(horizontal: 4),
|
||||||
),
|
),
|
||||||
if (data.profile.links.isNotEmpty)
|
if (data.profile.links.isNotEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileLinks(
|
child: _AccountProfileLinks(
|
||||||
data,
|
data: data,
|
||||||
).padding(horizontal: 4),
|
).padding(horizontal: 4),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileDetail(
|
child: _AccountPublisherList(
|
||||||
data,
|
publishers: accountPublishers.value ?? [],
|
||||||
|
).padding(horizontal: 4),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _AccountProfileDetail(
|
||||||
|
data: data,
|
||||||
).padding(horizontal: 4),
|
).padding(horizontal: 4),
|
||||||
),
|
),
|
||||||
if (user.value != null && !isCurrentUser)
|
if (user.value != null && !isCurrentUser)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountAction(data).padding(horizontal: 4),
|
child: _AccountAction(
|
||||||
|
data: data,
|
||||||
|
accountRelationship: accountRelationship,
|
||||||
|
accountChat: accountChat,
|
||||||
|
relationshipAction: relationshipAction,
|
||||||
|
blockAction: blockAction,
|
||||||
|
directMessageAction: directMessageAction,
|
||||||
|
).padding(horizontal: 4),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -769,7 +945,8 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
error:
|
error:
|
||||||
(error, stackTrace) => AppScaffold(
|
(error, stackTrace) => AppScaffold(
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
|||||||
@@ -762,5 +762,127 @@ class _AccountBotDeveloperProviderElement
|
|||||||
String get uname => (origin as AccountBotDeveloperProvider).uname;
|
String get uname => (origin as AccountBotDeveloperProvider).uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _$accountPublishersHash() => r'25f5695b4a5154163d77f1769876d826bf736609';
|
||||||
|
|
||||||
|
/// See also [accountPublishers].
|
||||||
|
@ProviderFor(accountPublishers)
|
||||||
|
const accountPublishersProvider = AccountPublishersFamily();
|
||||||
|
|
||||||
|
/// See also [accountPublishers].
|
||||||
|
class AccountPublishersFamily extends Family<AsyncValue<List<SnPublisher>>> {
|
||||||
|
/// See also [accountPublishers].
|
||||||
|
const AccountPublishersFamily();
|
||||||
|
|
||||||
|
/// See also [accountPublishers].
|
||||||
|
AccountPublishersProvider call(String id) {
|
||||||
|
return AccountPublishersProvider(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AccountPublishersProvider getProviderOverride(
|
||||||
|
covariant AccountPublishersProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'accountPublishersProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [accountPublishers].
|
||||||
|
class AccountPublishersProvider
|
||||||
|
extends AutoDisposeFutureProvider<List<SnPublisher>> {
|
||||||
|
/// See also [accountPublishers].
|
||||||
|
AccountPublishersProvider(String id)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => accountPublishers(ref as AccountPublishersRef, id),
|
||||||
|
from: accountPublishersProvider,
|
||||||
|
name: r'accountPublishersProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$accountPublishersHash,
|
||||||
|
dependencies: AccountPublishersFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
AccountPublishersFamily._allTransitiveDependencies,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
|
AccountPublishersProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.id,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<List<SnPublisher>> Function(AccountPublishersRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: AccountPublishersProvider._internal(
|
||||||
|
(ref) => create(ref as AccountPublishersRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
id: id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<List<SnPublisher>> createElement() {
|
||||||
|
return _AccountPublishersProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is AccountPublishersProvider && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, id.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin AccountPublishersRef on AutoDisposeFutureProviderRef<List<SnPublisher>> {
|
||||||
|
/// The parameter `id` of this provider.
|
||||||
|
String get id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountPublishersProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<List<SnPublisher>>
|
||||||
|
with AccountPublishersRef {
|
||||||
|
_AccountPublishersProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => (origin as AccountPublishersProvider).id;
|
||||||
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget {
|
|||||||
searchController.clear();
|
searchController.clear();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [query.value]);
|
}, [query]);
|
||||||
|
|
||||||
// Clean up timer on dispose
|
// Clean up timer on dispose
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'explore.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$activityListNotifierHash() =>
|
String _$activityListNotifierHash() =>
|
||||||
r'a4968856ac34b59d47cfd4a7cbb39289aef2a1b1';
|
r'167021cada54da7c8d8437eef1ffb387a92ea2e3';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -27,6 +27,224 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
|
|
||||||
part 'pub_profile.g.dart';
|
part 'pub_profile.g.dart';
|
||||||
|
|
||||||
|
class _PublisherBasisWidget extends StatelessWidget {
|
||||||
|
final SnPublisher data;
|
||||||
|
final AsyncValue<SnSubscriptionStatus> subStatus;
|
||||||
|
final ValueNotifier<bool> subscribing;
|
||||||
|
final VoidCallback subscribe;
|
||||||
|
final VoidCallback unsubscribe;
|
||||||
|
|
||||||
|
const _PublisherBasisWidget({
|
||||||
|
required this.data,
|
||||||
|
required this.subStatus,
|
||||||
|
required this.subscribing,
|
||||||
|
required this.subscribe,
|
||||||
|
required this.unsubscribe,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Badge(
|
||||||
|
isLabelVisible: data.type == 0,
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
label: Icon(
|
||||||
|
Symbols.launch,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
offset: Offset(0, 48),
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: data.picture,
|
||||||
|
radius: 32,
|
||||||
|
borderRadius: data.type == 0 ? null : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (data.account?.name != null) {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
context.pushNamed(
|
||||||
|
'accountProfile',
|
||||||
|
pathParameters: {'name': data.account!.name},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Text(data.nick).fontSize(20),
|
||||||
|
if (data.verification != null)
|
||||||
|
VerificationMark(mark: data.verification!),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'@${data.name}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).fontSize(14).opacity(0.85),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.type == 0 && data.account != null)
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
data.type == 0 ? Symbols.person : Symbols.workspaces,
|
||||||
|
fill: 1,
|
||||||
|
size: 17,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'publisherBelongsTo'.tr(args: ['@${data.account!.name}']),
|
||||||
|
).fontSize(14),
|
||||||
|
],
|
||||||
|
).opacity(0.85),
|
||||||
|
const Gap(4),
|
||||||
|
if (data.type == 0 && data.account != null)
|
||||||
|
AccountStatusWidget(
|
||||||
|
uname: data.account!.name,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
subStatus
|
||||||
|
.when(
|
||||||
|
data:
|
||||||
|
(status) => FilledButton.icon(
|
||||||
|
onPressed:
|
||||||
|
subscribing.value
|
||||||
|
? null
|
||||||
|
: (status.isSubscribed
|
||||||
|
? unsubscribe
|
||||||
|
: subscribe),
|
||||||
|
icon: Icon(
|
||||||
|
status.isSubscribed
|
||||||
|
? Symbols.remove_circle
|
||||||
|
: Symbols.add_circle,
|
||||||
|
),
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
status.isSubscribed
|
||||||
|
? 'unsubscribe'
|
||||||
|
: 'subscribe',
|
||||||
|
).tr(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(vertical: -2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error: (_, _) => const SizedBox(),
|
||||||
|
loading:
|
||||||
|
() => const SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.padding(top: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, top: 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherBadgesWidget extends StatelessWidget {
|
||||||
|
final SnPublisher data;
|
||||||
|
final AsyncValue<List<SnAccountBadge>> badges;
|
||||||
|
|
||||||
|
const _PublisherBadgesWidget({required this.data, required this.badges});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return (badges.value?.isNotEmpty ?? false)
|
||||||
|
? Card(
|
||||||
|
child: BadgeList(
|
||||||
|
badges: badges.value!,
|
||||||
|
).padding(horizontal: 26, vertical: 20),
|
||||||
|
).padding(horizontal: 4)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherVerificationWidget extends StatelessWidget {
|
||||||
|
final SnPublisher data;
|
||||||
|
|
||||||
|
const _PublisherVerificationWidget({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return (data.verification != null)
|
||||||
|
? Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: VerificationStatusCard(mark: data.verification!),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherBioWidget extends StatelessWidget {
|
||||||
|
final SnPublisher data;
|
||||||
|
|
||||||
|
const _PublisherBioWidget({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||||
|
if (data.bio.isEmpty)
|
||||||
|
Text('descriptionNone').tr().italic()
|
||||||
|
else
|
||||||
|
MarkdownTextContent(
|
||||||
|
content: data.bio,
|
||||||
|
linesMargin: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 16),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherCategoryTabWidget extends StatelessWidget {
|
||||||
|
final TabController categoryTabController;
|
||||||
|
|
||||||
|
const _PublisherCategoryTabWidget({required this.categoryTabController});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: TabBar(
|
||||||
|
controller: categoryTabController,
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
tabs: [
|
||||||
|
Tab(text: 'all'.tr()),
|
||||||
|
Tab(text: 'postTypePost'.tr()),
|
||||||
|
Tab(text: 'postArticle'.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnPublisher> publisher(Ref ref, String uname) async {
|
Future<SnPublisher> publisher(Ref ref, String uname) async {
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
@@ -132,170 +350,6 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
offset: Offset(1.0, 1.0),
|
offset: Offset(1.0, 1.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget publisherBasisWidget(SnPublisher data) => Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
child: Badge(
|
|
||||||
isLabelVisible: data.type == 0,
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
label: Icon(
|
|
||||||
Symbols.launch,
|
|
||||||
size: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
|
||||||
offset: Offset(0, 48),
|
|
||||||
child: ProfilePictureWidget(
|
|
||||||
file: data.picture,
|
|
||||||
radius: 32,
|
|
||||||
borderRadius: data.type == 0 ? null : 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
if (data.account?.name != null) {
|
|
||||||
context.pushNamed(
|
|
||||||
'accountProfile',
|
|
||||||
pathParameters: {'name': data.account!.name},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Text(data.nick).fontSize(20),
|
|
||||||
if (data.verification != null)
|
|
||||||
VerificationMark(mark: data.verification!),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'@${data.name}',
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
).fontSize(14).opacity(0.85),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (data.type == 0 && data.account != null)
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
data.type == 0 ? Symbols.person : Symbols.workspaces,
|
|
||||||
fill: 1,
|
|
||||||
size: 17,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'publisherBelongsTo'.tr(args: ['@${data.account!.name}']),
|
|
||||||
).fontSize(14),
|
|
||||||
],
|
|
||||||
).opacity(0.85),
|
|
||||||
const Gap(4),
|
|
||||||
if (data.type == 0 && data.account != null)
|
|
||||||
AccountStatusWidget(
|
|
||||||
uname: data.account!.name,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
subStatus
|
|
||||||
.when(
|
|
||||||
data:
|
|
||||||
(status) => FilledButton.icon(
|
|
||||||
onPressed:
|
|
||||||
subscribing.value
|
|
||||||
? null
|
|
||||||
: (status.isSubscribed
|
|
||||||
? unsubscribe
|
|
||||||
: subscribe),
|
|
||||||
icon: Icon(
|
|
||||||
status.isSubscribed
|
|
||||||
? Symbols.remove_circle
|
|
||||||
: Symbols.add_circle,
|
|
||||||
),
|
|
||||||
label:
|
|
||||||
Text(
|
|
||||||
status.isSubscribed
|
|
||||||
? 'unsubscribe'
|
|
||||||
: 'subscribe',
|
|
||||||
).tr(),
|
|
||||||
style: ButtonStyle(
|
|
||||||
visualDensity: VisualDensity(vertical: -2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
error: (_, _) => const SizedBox(),
|
|
||||||
loading:
|
|
||||||
() => const SizedBox(
|
|
||||||
height: 36,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.padding(top: 8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24, top: 24);
|
|
||||||
|
|
||||||
Widget publisherBadgesWidget(SnPublisher data) =>
|
|
||||||
(badges.value?.isNotEmpty ?? false)
|
|
||||||
? Card(
|
|
||||||
child: BadgeList(
|
|
||||||
badges: badges.value!,
|
|
||||||
).padding(horizontal: 26, vertical: 20),
|
|
||||||
).padding(horizontal: 4)
|
|
||||||
: const SizedBox.shrink();
|
|
||||||
|
|
||||||
Widget publisherVerificationWidget(SnPublisher data) =>
|
|
||||||
(data.verification != null)
|
|
||||||
? Card(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: VerificationStatusCard(mark: data.verification!),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink();
|
|
||||||
|
|
||||||
Widget publisherBioWidget(SnPublisher data) => Card(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
|
||||||
if (data.bio.isEmpty)
|
|
||||||
Text('descriptionNone').tr().italic()
|
|
||||||
else
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: data.bio,
|
|
||||||
linesMargin: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 20, vertical: 16),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget publisherCategoryTabWidget() => Card(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: TabBar(
|
|
||||||
controller: categoryTabController,
|
|
||||||
dividerColor: Colors.transparent,
|
|
||||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
tabs: [
|
|
||||||
Tab(text: 'all'.tr()),
|
|
||||||
Tab(text: 'postTypePost'.tr()),
|
|
||||||
Tab(text: 'postArticle'.tr()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return publisher.when(
|
return publisher.when(
|
||||||
data:
|
data:
|
||||||
(data) => AppScaffold(
|
(data) => AppScaffold(
|
||||||
@@ -351,7 +405,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
SliverGap(16),
|
SliverGap(16),
|
||||||
SliverPostList(pubName: name, pinned: true),
|
SliverPostList(pubName: name, pinned: true),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: publisherCategoryTabWidget(),
|
child: _PublisherCategoryTabWidget(
|
||||||
|
categoryTabController: categoryTabController,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SliverPostList(
|
SliverPostList(
|
||||||
key: ValueKey(categoryTab.value),
|
key: ValueKey(categoryTab.value),
|
||||||
@@ -377,10 +433,19 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
publisherBasisWidget(data).padding(bottom: 8),
|
_PublisherBasisWidget(
|
||||||
publisherBadgesWidget(data),
|
data: data,
|
||||||
publisherVerificationWidget(data),
|
subStatus: subStatus,
|
||||||
publisherBioWidget(data),
|
subscribing: subscribing,
|
||||||
|
subscribe: subscribe,
|
||||||
|
unsubscribe: unsubscribe,
|
||||||
|
).padding(bottom: 8),
|
||||||
|
_PublisherBadgesWidget(
|
||||||
|
data: data,
|
||||||
|
badges: badges,
|
||||||
|
),
|
||||||
|
_PublisherVerificationWidget(data: data),
|
||||||
|
_PublisherBioWidget(data: data),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -432,15 +497,32 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: publisherBasisWidget(data).padding(bottom: 8),
|
child: _PublisherBasisWidget(
|
||||||
|
data: data,
|
||||||
|
subStatus: subStatus,
|
||||||
|
subscribing: subscribing,
|
||||||
|
subscribe: subscribe,
|
||||||
|
unsubscribe: unsubscribe,
|
||||||
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: publisherBadgesWidget(data)),
|
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: publisherVerificationWidget(data),
|
child: _PublisherBadgesWidget(
|
||||||
|
data: data,
|
||||||
|
badges: badges,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _PublisherVerificationWidget(data: data),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _PublisherBioWidget(data: data),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
|
||||||
SliverPostList(pubName: name, pinned: true),
|
SliverPostList(pubName: name, pinned: true),
|
||||||
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
SliverToBoxAdapter(
|
||||||
|
child: _PublisherCategoryTabWidget(
|
||||||
|
categoryTabController: categoryTabController,
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverPostList(
|
SliverPostList(
|
||||||
key: ValueKey(categoryTab.value),
|
key: ValueKey(categoryTab.value),
|
||||||
pubName: name,
|
pubName: name,
|
||||||
|
|||||||
@@ -450,6 +450,20 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
title: Text('settingsDataSavingMode').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.data_saver_on_rounded),
|
||||||
|
trailing: Switch(
|
||||||
|
value: settings.dataSavingMode,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref
|
||||||
|
.read(appSettingsNotifierProvider.notifier)
|
||||||
|
.setDataSavingMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Desktop-specific settings
|
// Desktop-specific settings
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
|
|||||||
searchController.clear();
|
searchController.clear();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [query.value]);
|
}, [query]);
|
||||||
|
|
||||||
// Clean up timer on dispose
|
// Clean up timer on dispose
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||||
import 'package:file_saver/file_saver.dart';
|
import 'package:file_saver/file_saver.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:gal/gal.dart';
|
import 'package:gal/gal.dart';
|
||||||
@@ -804,155 +804,79 @@ class _CloudFileListEntry extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final dataSaving = ref.watch(
|
||||||
|
appSettingsNotifierProvider.select((s) => s.dataSavingMode),
|
||||||
|
);
|
||||||
final showMature = useState(false);
|
final showMature = useState(false);
|
||||||
|
final showDataSaving = useState(!dataSaving);
|
||||||
|
final lockedByDS = dataSaving && !showDataSaving.value;
|
||||||
|
final lockedByMature = file.sensitiveMarks.isNotEmpty && !showMature.value;
|
||||||
|
final meta = file.fileMeta is Map ? file.fileMeta as Map : const {};
|
||||||
|
final ratio = (meta['ratio'] is num && (meta['ratio'] as num) != 0)
|
||||||
|
? (meta['ratio'] as num).toDouble()
|
||||||
|
: 1.0;
|
||||||
|
|
||||||
var content = Stack(
|
Widget bg = const SizedBox.shrink();
|
||||||
fit: StackFit.expand,
|
if (isImage) {
|
||||||
children: [
|
if (meta['blur'] is String) {
|
||||||
if (isImage)
|
bg = BlurHash(hash: meta['blur'] as String);
|
||||||
Positioned.fill(
|
} else if (!lockedByDS && !lockedByMature) {
|
||||||
child:
|
bg = ImageFiltered(
|
||||||
file.fileMeta?['blur'] is String
|
|
||||||
? BlurHash(hash: file.fileMeta?['blur'])
|
|
||||||
: ImageFiltered(
|
|
||||||
imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||||
child: CloudFileWidget(item: file, noBlurhash: true),
|
child: CloudFileWidget(
|
||||||
|
item: file,
|
||||||
|
noBlurhash: true,
|
||||||
|
useInternalGate: false,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
if (isImage)
|
} else {
|
||||||
CloudFileWidget(
|
bg = const ColoredBox(color: Colors.black26);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool fullyUnlocked = !lockedByDS && !lockedByMature;
|
||||||
|
Widget fg = fullyUnlocked
|
||||||
|
? (isImage
|
||||||
|
? CloudFileWidget(
|
||||||
item: file,
|
item: file,
|
||||||
heroTag: heroTag,
|
heroTag: heroTag,
|
||||||
noBlurhash: true,
|
noBlurhash: true,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
|
useInternalGate: false,
|
||||||
)
|
)
|
||||||
else
|
: CloudFileWidget(
|
||||||
CloudFileWidget(item: file, heroTag: heroTag, fit: BoxFit.contain),
|
item: file,
|
||||||
],
|
heroTag: heroTag,
|
||||||
);
|
fit: BoxFit.contain,
|
||||||
|
useInternalGate: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: AspectRatio(aspectRatio: ratio, child: const SizedBox.shrink());
|
||||||
|
|
||||||
if (file.sensitiveMarks.isNotEmpty) {
|
Widget overlays;
|
||||||
// Show a blurred overlay only when not revealed yet, with a smooth transition
|
if (lockedByDS) {
|
||||||
content = Stack(
|
overlays = _DataSavingOverlay();
|
||||||
children: [
|
} else if (lockedByMature) {
|
||||||
content,
|
overlays = _SensitiveOverlay(file: file);
|
||||||
// Toggle blur overlay with animation
|
} else {
|
||||||
Positioned.fill(
|
overlays = const SizedBox.shrink();
|
||||||
child: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
switchInCurve: Curves.easeOut,
|
|
||||||
switchOutCurve: Curves.easeIn,
|
|
||||||
layoutBuilder:
|
|
||||||
(currentChild, previousChildren) => Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
...previousChildren,
|
|
||||||
if (currentChild != null) currentChild,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child:
|
|
||||||
showMature.value
|
|
||||||
? const SizedBox.shrink(key: ValueKey('revealed'))
|
|
||||||
: ColoredBox(
|
|
||||||
key: const ValueKey('blurred'),
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 64, sigmaY: 64),
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
const ColoredBox(color: Colors.transparent),
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.all(12),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black54,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
maxWidth: 280,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.warning,
|
|
||||||
color: Colors.white,
|
|
||||||
fill: 1,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
file.sensitiveMarks
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
SensitiveCategory
|
|
||||||
.values[e]
|
|
||||||
.i18nKey
|
|
||||||
.tr(),
|
|
||||||
)
|
|
||||||
.join(' · '),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Sensitive Content',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
'Tap to Reveal',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).padding(horizontal: 24, vertical: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// When revealed (no blur), show a small control at top-left to re-enable blur
|
|
||||||
if (showMature.value)
|
|
||||||
Positioned(
|
|
||||||
top: 3,
|
|
||||||
left: 4,
|
|
||||||
child: IconButton(
|
|
||||||
iconSize: 16,
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
icon: const Icon(Icons.visibility_off, color: Colors.white),
|
|
||||||
tooltip: 'Blur content',
|
|
||||||
onPressed: () {
|
|
||||||
showMature.value = false;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onTap != null) {
|
final content = Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
if (isImage) Positioned.fill(child: bg),
|
||||||
|
fg,
|
||||||
|
overlays,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!showMature.value) {
|
if (lockedByDS) {
|
||||||
|
showDataSaving.value = true;
|
||||||
|
} else if (lockedByMature) {
|
||||||
showMature.value = true;
|
showMature.value = true;
|
||||||
} else {
|
} else {
|
||||||
onTap?.call();
|
onTap?.call();
|
||||||
@@ -961,7 +885,88 @@ class _CloudFileListEntry extends HookConsumerWidget {
|
|||||||
child: content,
|
child: content,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return content;
|
class _SensitiveOverlay extends StatelessWidget {
|
||||||
|
final SnCloudFile file;
|
||||||
|
const _SensitiveOverlay({required this.file});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 64, sigmaY: 64),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Center(
|
||||||
|
child: _OverlayCard(
|
||||||
|
icon: Icons.warning,
|
||||||
|
title: file.sensitiveMarks
|
||||||
|
.map((e) => SensitiveCategory.values[e].i18nKey.tr())
|
||||||
|
.join(' · '),
|
||||||
|
subtitle: 'Sensitive Content',
|
||||||
|
hint: 'Tap to Reveal',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DataSavingOverlay extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ColoredBox(
|
||||||
|
color: Colors.black38,
|
||||||
|
child: Center(
|
||||||
|
child: _OverlayCard(
|
||||||
|
icon: Symbols.image,
|
||||||
|
title: 'Data Saving Mode',
|
||||||
|
subtitle: '',
|
||||||
|
hint: 'Tap to Load',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class _OverlayCard extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final String hint;
|
||||||
|
|
||||||
|
const _OverlayCard({
|
||||||
|
required this.icon,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
required this.hint,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black54,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 280),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Colors.white, size: 24),
|
||||||
|
const Gap(4),
|
||||||
|
Text(title,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white, fontWeight: FontWeight.w600),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
Text(subtitle,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 13)),
|
||||||
|
const Gap(4),
|
||||||
|
Text(hint,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 11)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:island/widgets/content/audio.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:island/widgets/data_saving_gate.dart';
|
||||||
|
|
||||||
import 'image.dart';
|
import 'image.dart';
|
||||||
import 'video.dart';
|
import 'video.dart';
|
||||||
@@ -23,40 +24,51 @@ class CloudFileWidget extends HookConsumerWidget {
|
|||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final String? heroTag;
|
final String? heroTag;
|
||||||
final bool noBlurhash;
|
final bool noBlurhash;
|
||||||
|
final bool useInternalGate;
|
||||||
const CloudFileWidget({
|
const CloudFileWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.heroTag,
|
this.heroTag,
|
||||||
this.noBlurhash = false,
|
this.noBlurhash = false,
|
||||||
|
this.useInternalGate = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final dataSaving = ref.watch(
|
||||||
|
appSettingsNotifierProvider.select((s) => s.dataSavingMode),
|
||||||
|
);
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/drive/files/${item.id}';
|
final uri = '$serverUrl/drive/files/${item.id}';
|
||||||
|
|
||||||
var ratio =
|
final unlocked = useState(false);
|
||||||
item.fileMeta?['ratio'] is num
|
|
||||||
? item.fileMeta!['ratio'].toDouble()
|
final meta = item.fileMeta is Map ? (item.fileMeta as Map) : const {};
|
||||||
: 1.0;
|
final blurHash = noBlurhash ? null : (meta['blur'] as String?);
|
||||||
|
var ratio = meta['ratio'] is num ? (meta['ratio'] as num).toDouble() : 1.0;
|
||||||
if (ratio == 0) ratio = 1.0;
|
if (ratio == 0) ratio = 1.0;
|
||||||
|
|
||||||
|
Widget cloudImage() => UniversalImage(uri: uri, blurHash: blurHash, fit: fit);
|
||||||
|
Widget cloudVideo() => CloudVideoWidget(item: item);
|
||||||
|
|
||||||
|
Widget dataPlaceHolder(IconData icon) => _DataSavingPlaceholder(
|
||||||
|
icon: icon,
|
||||||
|
onTap: () {
|
||||||
|
unlocked.value = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
var content = switch (item.mimeType?.split('/').firstOrNull) {
|
var content = switch (item.mimeType?.split('/').firstOrNull) {
|
||||||
"image" => AspectRatio(
|
'image' => AspectRatio(
|
||||||
aspectRatio: ratio,
|
aspectRatio: ratio,
|
||||||
child: UniversalImage(
|
child: (useInternalGate && dataSaving && !unlocked.value) ? dataPlaceHolder(Symbols.image) : cloudImage(),
|
||||||
uri: uri,
|
|
||||||
blurHash:
|
|
||||||
noBlurhash
|
|
||||||
? null
|
|
||||||
: (item.fileMeta is String ? item.fileMeta!['blur'] : null),
|
|
||||||
),
|
),
|
||||||
),
|
'video' => AspectRatio(
|
||||||
"video" => AspectRatio(
|
|
||||||
aspectRatio: ratio,
|
aspectRatio: ratio,
|
||||||
child: CloudVideoWidget(item: item),
|
child: (useInternalGate && dataSaving && !unlocked.value) ? dataPlaceHolder(Symbols.play_arrow) : cloudVideo(),
|
||||||
),
|
),
|
||||||
"audio" => Center(
|
'audio' => Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8),
|
maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8),
|
||||||
@@ -113,6 +125,35 @@ class CloudFileWidget extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DataSavingPlaceholder extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
const _DataSavingPlaceholder({required this.icon, required this.onTap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black26,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 36,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'dataSavingHint'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
class CloudVideoWidget extends HookConsumerWidget {
|
class CloudVideoWidget extends HookConsumerWidget {
|
||||||
final SnCloudFile item;
|
final SnCloudFile item;
|
||||||
const CloudVideoWidget({super.key, required this.item});
|
const CloudVideoWidget({super.key, required this.item});
|
||||||
@@ -314,29 +355,32 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/drive/files/${file?.id ?? fileId}';
|
final String? id = file?.id ?? fileId;
|
||||||
|
|
||||||
|
final fallback = Icon(
|
||||||
|
fallbackIcon ?? Symbols.account_circle,
|
||||||
|
size: radius,
|
||||||
|
color: fallbackColor ?? Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
).center();
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius:
|
borderRadius: borderRadius == null
|
||||||
borderRadius == null
|
|
||||||
? BorderRadius.all(Radius.circular(radius))
|
? BorderRadius.all(Radius.circular(radius))
|
||||||
: BorderRadius.all(Radius.circular(borderRadius!)),
|
: BorderRadius.all(Radius.circular(borderRadius!)),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: radius * 2,
|
width: radius * 2,
|
||||||
height: radius * 2,
|
height: radius * 2,
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child:
|
child: id == null
|
||||||
file != null
|
? fallback
|
||||||
? CloudFileWidget(item: file!, fit: BoxFit.cover)
|
: DataSavingGate(
|
||||||
: fileId == null
|
bypass: true,
|
||||||
? Icon(
|
placeholder: fallback,
|
||||||
fallbackIcon ?? Symbols.account_circle,
|
content: () => UniversalImage(
|
||||||
size: radius,
|
uri: '$serverUrl/drive/files/$id',
|
||||||
color:
|
fit: BoxFit.cover,
|
||||||
fallbackColor ??
|
),
|
||||||
Theme.of(context).colorScheme.onPrimaryContainer,
|
),
|
||||||
).center()
|
|
||||||
: UniversalImage(uri: uri, fit: BoxFit.cover),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
27
lib/widgets/data_saving_gate.dart
Normal file
27
lib/widgets/data_saving_gate.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
|
||||||
|
|
||||||
|
typedef WidgetBuilder0 = Widget Function();
|
||||||
|
|
||||||
|
class DataSavingGate extends ConsumerWidget {
|
||||||
|
final bool bypass;
|
||||||
|
final WidgetBuilder0 content;
|
||||||
|
final Widget placeholder;
|
||||||
|
|
||||||
|
const DataSavingGate({
|
||||||
|
super.key,
|
||||||
|
required this.bypass,
|
||||||
|
required this.content,
|
||||||
|
required this.placeholder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final dataSaving =
|
||||||
|
ref.watch(appSettingsNotifierProvider.select((s) => s.dataSavingMode));
|
||||||
|
if (bypass || !dataSaving) return content();
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
|||||||
'PostFeaturedList: isCollapsed changed to ${isCollapsed.value}',
|
'PostFeaturedList: isCollapsed changed to ${isCollapsed.value}',
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}, [isCollapsed.value]);
|
}, [isCollapsed]);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (featuredPostsAsync.hasValue && featuredPostsAsync.value!.isNotEmpty) {
|
if (featuredPostsAsync.hasValue && featuredPostsAsync.value!.isNotEmpty) {
|
||||||
@@ -93,7 +93,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [featuredPostsAsync.value]);
|
}, [featuredPostsAsync]);
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ class PostShuffleScreen extends HookConsumerWidget {
|
|||||||
bottom:
|
bottom:
|
||||||
kBottomControlHeight + MediaQuery.of(context).padding.bottom,
|
kBottomControlHeight + MediaQuery.of(context).padding.bottom,
|
||||||
),
|
),
|
||||||
child:
|
child: Builder(
|
||||||
(postListState.value?.items.length ?? 0) > 0
|
key: ValueKey(postListState.value?.items.length ?? 0),
|
||||||
? CardSwiper(
|
builder: (context) {
|
||||||
|
if ((postListState.value?.items.length ?? 0) > 0) {
|
||||||
|
return CardSwiper(
|
||||||
controller: cardSwiperController,
|
controller: cardSwiperController,
|
||||||
cardsCount: postListState.value!.items.length,
|
cardsCount: postListState.value!.items.length,
|
||||||
isLoop: false,
|
isLoop: false,
|
||||||
@@ -67,15 +69,17 @@ class PostShuffleScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onEnd: () {
|
onEnd: () async {
|
||||||
if (postListState.value?.hasMore ?? true) {
|
if (postListState.value?.hasMore ?? true) {
|
||||||
postListNotifier.fetch(
|
postListNotifier.forceRefresh();
|
||||||
cursor: postListState.value?.nextCursor,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
: Center(child: CircularProgressIndicator()),
|
} else {
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user