218 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:dio/dio.dart';
 | 
						|
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
						|
import 'package:gap/gap.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:island/models/account.dart';
 | 
						|
import 'package:island/pods/network.dart';
 | 
						|
import 'package:island/pods/userinfo.dart';
 | 
						|
import 'package:island/widgets/account/status.dart';
 | 
						|
import 'package:island/widgets/alert.dart';
 | 
						|
import 'package:island/widgets/content/sheet.dart';
 | 
						|
import 'package:material_symbols_icons/symbols.dart';
 | 
						|
 | 
						|
class AccountStatusCreationSheet extends HookConsumerWidget {
 | 
						|
  final SnAccountStatus? initialStatus;
 | 
						|
  const AccountStatusCreationSheet({super.key, this.initialStatus});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final attitude = useState<int>(initialStatus?.attitude ?? 1);
 | 
						|
    final isInvisible = useState(initialStatus?.isInvisible ?? false);
 | 
						|
    final isNotDisturb = useState(initialStatus?.isNotDisturb ?? false);
 | 
						|
    final clearedAt = useState<DateTime?>(initialStatus?.clearedAt);
 | 
						|
    final labelController = useTextEditingController(
 | 
						|
      text: initialStatus?.label ?? '',
 | 
						|
    );
 | 
						|
 | 
						|
    final submitting = useState(false);
 | 
						|
 | 
						|
    Future<void> clearStatus() async {
 | 
						|
      try {
 | 
						|
        submitting.value = true;
 | 
						|
        final user = ref.watch(userInfoProvider);
 | 
						|
        final apiClient = ref.read(apiClientProvider);
 | 
						|
        await apiClient.delete('/pass/accounts/me/statuses');
 | 
						|
        if (!context.mounted) return;
 | 
						|
        ref.invalidate(accountStatusProvider(user.value!.name));
 | 
						|
        Navigator.pop(context);
 | 
						|
      } catch (err) {
 | 
						|
        showErrorAlert(err);
 | 
						|
      } finally {
 | 
						|
        submitting.value = false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    Future<void> submitStatus() async {
 | 
						|
      try {
 | 
						|
        submitting.value = true;
 | 
						|
        final user = ref.watch(userInfoProvider);
 | 
						|
        final apiClient = ref.read(apiClientProvider);
 | 
						|
        await apiClient.request(
 | 
						|
          '/pass/accounts/me/statuses',
 | 
						|
          data: {
 | 
						|
            'attitude': attitude.value,
 | 
						|
            'is_invisible': isInvisible.value,
 | 
						|
            'is_not_disturb': isNotDisturb.value,
 | 
						|
            'cleared_at': clearedAt.value?.toUtc().toIso8601String(),
 | 
						|
            if (labelController.text.isNotEmpty) 'label': labelController.text,
 | 
						|
          },
 | 
						|
          options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
 | 
						|
        );
 | 
						|
        if (user.value != null) {
 | 
						|
          ref.invalidate(accountStatusProvider(user.value!.name));
 | 
						|
        }
 | 
						|
        if (!context.mounted) return;
 | 
						|
        Navigator.pop(context);
 | 
						|
      } catch (err) {
 | 
						|
        showErrorAlert(err);
 | 
						|
      } finally {
 | 
						|
        submitting.value = false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return SheetScaffold(
 | 
						|
      heightFactor: 0.6,
 | 
						|
      titleText:
 | 
						|
          initialStatus == null ? 'statusCreate'.tr() : 'statusUpdate'.tr(),
 | 
						|
      actions: [
 | 
						|
        TextButton.icon(
 | 
						|
          onPressed:
 | 
						|
              submitting.value
 | 
						|
                  ? null
 | 
						|
                  : () {
 | 
						|
                    submitStatus();
 | 
						|
                  },
 | 
						|
          icon: const Icon(Symbols.upload),
 | 
						|
          label: Text(initialStatus == null ? 'create' : 'update').tr(),
 | 
						|
          style: ButtonStyle(
 | 
						|
            visualDensity: VisualDensity(
 | 
						|
              horizontal: VisualDensity.minimumDensity,
 | 
						|
            ),
 | 
						|
            foregroundColor: WidgetStatePropertyAll(
 | 
						|
              Theme.of(context).colorScheme.onSurface,
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
        if (initialStatus != null)
 | 
						|
          IconButton(
 | 
						|
            icon: const Icon(Symbols.delete),
 | 
						|
            onPressed: submitting.value ? null : () => clearStatus(),
 | 
						|
            style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
 | 
						|
          ),
 | 
						|
      ],
 | 
						|
      child: SingleChildScrollView(
 | 
						|
        padding: const EdgeInsets.symmetric(horizontal: 20),
 | 
						|
        child: Column(
 | 
						|
          crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
						|
          children: [
 | 
						|
            const Gap(24),
 | 
						|
            TextField(
 | 
						|
              controller: labelController,
 | 
						|
              decoration: InputDecoration(
 | 
						|
                labelText: 'statusLabel'.tr(),
 | 
						|
                border: const OutlineInputBorder(
 | 
						|
                  borderRadius: BorderRadius.all(Radius.circular(12)),
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
              onTapOutside:
 | 
						|
                  (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
						|
            ),
 | 
						|
            const SizedBox(height: 24),
 | 
						|
            Text(
 | 
						|
              'statusAttitude'.tr(),
 | 
						|
              style: Theme.of(context).textTheme.titleMedium,
 | 
						|
            ),
 | 
						|
            const SizedBox(height: 8),
 | 
						|
            SegmentedButton(
 | 
						|
              segments: [
 | 
						|
                ButtonSegment(
 | 
						|
                  value: 0,
 | 
						|
                  icon: const Icon(Symbols.sentiment_satisfied),
 | 
						|
                  label: Text('attitudePositive'.tr()),
 | 
						|
                ),
 | 
						|
                ButtonSegment(
 | 
						|
                  value: 1,
 | 
						|
                  icon: const Icon(Symbols.sentiment_stressed),
 | 
						|
                  label: Text('attitudeNeutral'.tr()),
 | 
						|
                ),
 | 
						|
                ButtonSegment(
 | 
						|
                  value: 2,
 | 
						|
                  icon: const Icon(Symbols.sentiment_sad),
 | 
						|
                  label: Text('attitudeNegative'.tr()),
 | 
						|
                ),
 | 
						|
              ],
 | 
						|
              selected: {attitude.value},
 | 
						|
              onSelectionChanged: (Set<int> newSelection) {
 | 
						|
                attitude.value = newSelection.first;
 | 
						|
              },
 | 
						|
            ),
 | 
						|
            const Gap(12),
 | 
						|
            SwitchListTile(
 | 
						|
              title: Text('statusInvisible'.tr()),
 | 
						|
              subtitle: Text('statusInvisibleDescription'.tr()),
 | 
						|
              value: isInvisible.value,
 | 
						|
              contentPadding: EdgeInsets.symmetric(horizontal: 8),
 | 
						|
              onChanged: (bool value) {
 | 
						|
                isInvisible.value = value;
 | 
						|
              },
 | 
						|
            ),
 | 
						|
            SwitchListTile(
 | 
						|
              title: Text('statusNotDisturb'.tr()),
 | 
						|
              subtitle: Text('statusNotDisturbDescription'.tr()),
 | 
						|
              value: isNotDisturb.value,
 | 
						|
              contentPadding: EdgeInsets.symmetric(horizontal: 8),
 | 
						|
              onChanged: (bool value) {
 | 
						|
                isNotDisturb.value = value;
 | 
						|
              },
 | 
						|
            ),
 | 
						|
            const SizedBox(height: 24),
 | 
						|
            Text(
 | 
						|
              'statusClearTime'.tr(),
 | 
						|
              style: Theme.of(context).textTheme.titleMedium,
 | 
						|
            ),
 | 
						|
            const SizedBox(height: 8),
 | 
						|
            ListTile(
 | 
						|
              title: Text(
 | 
						|
                clearedAt.value == null
 | 
						|
                    ? 'statusNoAutoClear'.tr()
 | 
						|
                    : DateFormat.yMMMd().add_jm().format(clearedAt.value!),
 | 
						|
              ),
 | 
						|
              trailing: const Icon(Symbols.schedule),
 | 
						|
              shape: RoundedRectangleBorder(
 | 
						|
                borderRadius: BorderRadius.circular(8),
 | 
						|
                side: BorderSide(color: Theme.of(context).colorScheme.outline),
 | 
						|
              ),
 | 
						|
              onTap: () async {
 | 
						|
                final now = DateTime.now();
 | 
						|
                final date = await showDatePicker(
 | 
						|
                  context: context,
 | 
						|
                  initialDate: now,
 | 
						|
                  firstDate: now,
 | 
						|
                  lastDate: now.add(const Duration(days: 365)),
 | 
						|
                );
 | 
						|
                if (date == null) return;
 | 
						|
                if (!context.mounted) return;
 | 
						|
                final time = await showTimePicker(
 | 
						|
                  context: context,
 | 
						|
                  initialTime: TimeOfDay.now(),
 | 
						|
                );
 | 
						|
                if (time == null) return;
 | 
						|
                clearedAt.value = DateTime(
 | 
						|
                  date.year,
 | 
						|
                  date.month,
 | 
						|
                  date.day,
 | 
						|
                  time.hour,
 | 
						|
                  time.minute,
 | 
						|
                );
 | 
						|
              },
 | 
						|
            ),
 | 
						|
            Gap(MediaQuery.of(context).padding.bottom + 24),
 | 
						|
          ],
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |