DEV Community

kanta13jp1
kanta13jp1

Posted on

Wiki + Time Tracker + Voice Memo in Flutter Web: SegmentedButton, LinearProgressIndicator Bar Chart & Flutter 3.33 Deprecations

Wiki + Time Tracker + Voice Memo in Flutter Web

What We Shipped

Three features in one session for 自分株式会社:

  1. Wiki Database — competing with Notion, Confluence (hierarchical pages + table view)
  2. Time Tracker — competing with Jobcan, Toggl, Clockify (clock-in/out + project-based tracking)
  3. Voice Memo Transcriber — competing with Google Keep, LINE Keep (voice + AI summary + note conversion)

All three use the same Edge Function First architecture.


Common Pattern: Edge Function First

Every page follows the same fetch pattern:

Future<void> _fetchData() async {
  setState(() => _isLoading = true);
  try {
    final response = await _supabase.functions.invoke(
      'edge-function-name',
      queryParameters: {'view': 'list'},
    );
    final data = response.data as Map<String, dynamic>?;
    if (data?['items'] is List) {
      setState(() => _items = (data!['items'] as List).cast<Map<String, dynamic>>());
    }
  } catch (e) {
    setState(() => _errorMessage = 'Failed to load: $e');
  } finally {
    if (mounted) setState(() => _isLoading = false);
  }
}
Enter fullscreen mode Exit fullscreen mode

Flutter only renders. All validation, DB access, and business logic live in the Deno Edge Function.


Wiki Database: Hierarchical Pages

Parallel Child + Table Fetch

Future<void> _fetchPageDetail(String pageId) async {
  final response = await _supabase.functions.invoke(
    'wiki-database',
    queryParameters: {'view': 'page', 'page_id': pageId},
  );
  // Returns page, children[], and tableRows[] in one response
}
Enter fullscreen mode Exit fullscreen mode

One call returns the page content, its child pages, and table rows together. The UI is a 2-tab TabBarView — selecting a page auto-switches to the detail tab.


Time Tracker: SegmentedButton + Bar Chart

Period Selector

SegmentedButton<String>(
  segments: const [
    ButtonSegment(value: 'today', label: Text('Today')),
    ButtonSegment(value: 'week',  label: Text('This Week')),
    ButtonSegment(value: 'month', label: Text('This Month')),
  ],
  selected: {_view},
  onSelectionChanged: (s) {
    setState(() => _view = s.first);
    _fetchEntries();
  },
),
Enter fullscreen mode Exit fullscreen mode

SegmentedButton is Material 3 — no radio buttons or custom toggle needed.

LinearProgressIndicator as Bar Chart

Project-time breakdown rendered with LinearProgressIndicator:

final ratio = maxHours > 0 ? hours / maxHours : 0.0;
LinearProgressIndicator(
  value: ratio.toDouble(),
  valueColor: AlwaysStoppedAnimation<Color>(color),
  minHeight: 8,
)
Enter fullscreen mode Exit fullscreen mode

Normalize each project's hours against the maximum — LinearProgressIndicator does the rendering. No chart package needed for a simple bar chart.

Overtime Alert

if (data?['overtimeAlert'] == true)
  Container(
    padding: const EdgeInsets.all(8),
    color: Colors.orange.shade100,
    child: const Text('⚠️ Overtime detected today'),
  ),
Enter fullscreen mode Exit fullscreen mode

The Edge Function sets overtimeAlert: true when daily hours exceed the threshold. Flutter just shows or hides the banner.


Voice Memo: Search + ExpansionTile + Note Conversion

Server-Side Search

Future<void> _searchMemos(String q) async {
  final response = await _supabase.functions.invoke(
    'voice-memo-transcriber',
    queryParameters: {'view': 'search', 'q': q.trim()},
  );
}
Enter fullscreen mode Exit fullscreen mode

Search runs in the Edge Function with a full-text or ILIKE query — no client-side filtering.

ExpansionTile for Summary + Transcript

Each memo item uses ExpansionTile: collapsed shows title + duration, expanded shows AI summary and full transcript. No extra state management needed.

Convert to Note

A "Convert to Note" button saves AI summary + transcript as Markdown to the notes table:

TextButton.icon(
  onPressed: () => _convertToNote(memo),
  icon: const Icon(Icons.note_add, size: 16),
  label: const Text('Save as Note'),
)
Enter fullscreen mode Exit fullscreen mode

The Edge Function generates the Markdown and does the insert — Flutter fires the action and shows a snackbar.


Flutter 3.33 Deprecation Fixes

DropdownButtonFormField.valueinitialValue

// DEPRECATED
DropdownButtonFormField<String>(
  value: _selectedStyle,
  ...
)

// CORRECT
DropdownButtonFormField<String>(
  initialValue: _selectedStyle,
  ...
)
Enter fullscreen mode Exit fullscreen mode

flutter analyze catches this as deprecated_member_use. The rename is mechanical — valueinitialValue, no behavioral change.

require_trailing_commas

Multi-line widget arguments need a trailing comma on every last argument:

// WRONG — lint error
subtitle: Text(
  longText,
  style: const TextStyle(fontSize: 12)),  // missing trailing comma

// CORRECT
subtitle: Text(
  longText,
  style: const TextStyle(fontSize: 12),
),
Enter fullscreen mode Exit fullscreen mode

Easy to miss inside ternary expressions. flutter analyze catches all of them — run it before every commit.


Key Takeaways

Pattern What it solves
Single EF call returns all tab data Avoids waterfall fetches on tab switches
LinearProgressIndicator as bar chart No chart package for simple visualizations
SegmentedButton for period filter Material 3 toggle, zero custom code
ExpansionTile for memo detail Progressive disclosure without state management
Overtime flag from EF Business logic stays server-side

Try it: 自分株式会社

buildinpublic #Flutter #Supabase #Dart #webdev

Top comments (0)