code milestone 4

紅寶鐵軌客
Join to follow...
Follow/Unfollow Writer: 紅寶鐵軌客
By following, you’ll receive notifications when this author publishes new articles.
Don't wait! Sign up to follow this writer.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.
寫程式中、折磨中、享受中 ......
590   0  
·
2021/07/11
·
5 mins read


這是 milestone 4 的程式碼:

lib/main.dart:

import 'package:flutter/material.dart';
import 'package:happy_recorder/screens/audio_session.dart';
import 'package:happy_recorder/screens/my_home_page.dart';
import 'package:happy_recorder/screens/page_404.dart';
import 'package:happy_recorder/theme/style.dart';
import 'package:happy_recorder/blocs/my_theme_mode.dart';
import 'package:provider/provider.dart';

void main() {
  return runApp(ChangeNotifierProvider<MyThemeMode>(
    create: (_) => new MyThemeMode(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Happy Recorder',
      theme: themeCustomLight, // default theme
      darkTheme: themeCustomDark, // dark theme
      themeMode: context.watch<MyThemeMode>().getMode,
      //themeMode: Provider.of<MyThemeMode>(context, listen: true).getMode,
      initialRoute: '/',
      routes: {
        '/': (context) => MyHomePage(title: 'Happy Recorder v0.1'),
        '/audio': (context) => AudioSession(
            arIndex: (ModalRoute.of(context)!.settings.arguments as int)),
      },
      onUnknownRoute: (RouteSettings settings) {
        return MaterialPageRoute(
            builder: (_) => Page404(routeName: settings.name));
      },
    );
  }
}


lib/theme/style.dart:

import 'package:flutter/material.dart';

//
// 完全使用 Material light/dark 預設
//
final themeLight = ThemeData.light().copyWith(
  primaryColor: Colors.deepPurple,
  /*
  // fontFamily 不能用
  // textTheme 等等設定需要依照 light 及 dart 各設定一次,非常不 DRY
  textTheme: ThemeData.light().textTheme.copyWith(
    headline1: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold),
    headline6: TextStyle(fontSize: 18.0, fontStyle: FontStyle.italic),
    bodyText2: TextStyle(fontSize: 15.0, fontFamily: 'Hind'),
  ),
  */
);
final themeDark = ThemeData.dark().copyWith(
  primaryColor: Colors.blueGrey,
);

//
// 使用 ThemeData.from 設定色盤
//
final themeFromLight = ThemeData.from(
  // 只能設定 textTheme 及 colorScheme, 其他通通都不能用
  textTheme: myBaseTextTheme,
  colorScheme: const ColorScheme.light(
          //secondary: Colors.red,
          )
      .copyWith(
    primary: Colors.deepPurple,
  ),
);
final themeFromDark = ThemeData.from(
  textTheme: myBaseTextTheme,
  colorScheme: const ColorScheme.dark(
          //secondary: Colors.red,
          )
      .copyWith(
    primary: Colors.blueGrey,
  ),
);

//
// 自定 Theme
//
final themeCustomLight = ThemeData(
  brightness: Brightness.light,
  colorScheme: ColorScheme.light().copyWith(
      primary: Colors.pink,
      secondary: Colors.pinkAccent,
      background: Colors.blueGrey[100]),
  //accentColor no longer work, https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties
  textTheme: myBaseTextTheme,
  // fontFamily: 'Georgia', // 還是 light, dark 都要寫一次
  appBarTheme: AppBarTheme(
    backgroundColor: Colors.pink,
  ),
  bottomAppBarTheme: BottomAppBarTheme(
    color: Colors.pink,
  ),
  floatingActionButtonTheme: myFabTheme,
  cardTheme: myCardTheme,
);

final themeCustomDark = ThemeData(
  brightness: Brightness.dark,
  colorScheme: ColorScheme.dark().copyWith(primary: Colors.blue),
  textTheme: myBaseTextTheme,
  floatingActionButtonTheme: myFabTheme,
  cardTheme: myCardTheme,
);

const myFabTheme = FloatingActionButtonThemeData(
  foregroundColor: Colors.yellow,
  backgroundColor: Colors.pink,
  shape: CircleBorder(
    side: BorderSide(
      width: 3,
      color: Colors.red,
      style: BorderStyle.solid,
    ),
  ),
);

const myBaseTextTheme = TextTheme(
  headline5: TextStyle(
      fontSize: 20.0, fontWeight: FontWeight.w800, fontFamily: 'JBMono'),
  headline6: TextStyle(
      fontSize: 18.0, fontWeight: FontWeight.w600, fontFamily: 'JBMono'),
  subtitle1:
      TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold), // ListTile title
  subtitle2: TextStyle(
      fontSize: 18.0, fontWeight: FontWeight.normal, fontFamily: 'JBMono'),
  bodyText2: TextStyle(
      fontSize: 16.0, fontStyle: FontStyle.italic), // ListTile subtitle
  button: TextStyle(height: 2.0),
  caption: TextStyle(fontFamily: 'JBMono'),
);

const myCardTheme = CardTheme(
  margin: EdgeInsets.all(5),
);


lib/theme/custom_widgets.dart:

import 'package:flutter/material.dart';

// custom widgets

//  Icon on Text with style
//    Use: Draw Toggle btn,
Widget customIconOnTextWidget(String label, IconData icon, TextStyle? textStyle) {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Icon(icon),
      Text(label, style: textStyle),
    ],
  );
}


lib/screens/my_home_page.dart:

import 'package:flutter/material.dart';
import 'package:happy_recorder/models/audio_rec.dart';
import 'package:happy_recorder/blocs/my_theme_mode.dart';
import 'package:happy_recorder/theme/custom_widgets.dart';
import 'package:provider/provider.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title})
      : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  //int _counter = 0;
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  List<bool> _themeModeSelected() {
    switch (context.read<MyThemeMode>().getMode) {
      // toggle btn in order of Light/Dart/System
      case ThemeMode.dark:
        return [false, true, false];
      case ThemeMode.light:
        return [true, false, false];
      default: // anything else is ThemeMode.System, this also avoid potential nullable warning.
        return [false, false, true];
    }
  }

  void _openDrawer() {
    _scaffoldKey.currentState!.openDrawer(); // "!" is “Casting away nullability” = never null
  } // _openDrawer()

  void _closeDrawer() {
    Navigator.of(context).pop();
  } // _closeDrawer()

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).colorScheme.background,
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemCount: audioRec.length,
        itemBuilder: (context, index) {
          return Hero(
            tag: 'audio_rec_$index',
            child: Card(
              child: ListTile(
                title: Text(audioRec[index].title),
                subtitle: Text(audioRec[index].description),
                onTap: () {
                  Navigator.pushNamed(context, '/audio', arguments: index);
                },
                trailing: Icon(
                  Icons.play_arrow,
                  color: Theme.of(context).colorScheme.secondary,
                  //color: cardListArrow,
                  //semanticLabel: 'Play $audioRec[index].title audio',
                ),
              ),
            ),
          );
        },
      ),
      drawer: Drawer(
        child: ListView(
          padding: Theme.of(context).cardTheme.margin,
          children: <Widget>[
            Container(
              // top placeholder block, iPhone front camera will block this.
              height: 50,
              color: Colors.transparent,
            ),
            Container(
              color: Theme.of(context).colorScheme.background,
              margin: Theme.of(context).cardTheme.margin,
              height: 90,
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  children: [
                    Text(
                      'Select your favor theme',
                      style: Theme.of(context).textTheme.button,
                    ),
                    ToggleButtons(
                      children: [
                        customIconOnTextWidget('light', Icons.light_mode, Theme.of(context).textTheme.caption),
                        customIconOnTextWidget('dark', Icons.dark_mode, Theme.of(context).textTheme.caption),
                        customIconOnTextWidget('system', Icons.devices, Theme.of(context).textTheme.caption),
                      ],
                      onPressed: (int index) {
                        //setState(() {
                          for (int vI = 0; vI < _themeModeSelected().length; vI++) {
                            if (vI == index) {
                              _themeModeSelected()[vI] = true;
                            } else {
                              _themeModeSelected()[vI] = false;
                            }
                          }
                          switch (index) {
                            // btn order: 0/light, 1/dark, 2/System
                            case 0:
                              //Provider.of<MyThemeMode>(context, listen: false).setModeTo(ThemeMode.light);
                              context.read<MyThemeMode>().setModeTo(ThemeMode.light);
                              break;
                            case 1:
                              context.read<MyThemeMode>().setModeTo(ThemeMode.dark);
                              break;
                            case 2:
                              context.read<MyThemeMode>().setModeTo(ThemeMode.system);
                              break;
                          }
                        //}); // setState()
                      },
                      isSelected: _themeModeSelected(),
                    ),
                  ],
                ),
              ),
            ),
            Container(
              // This is a placeholder for future reference.
              color: Theme.of(context).colorScheme.background,
              margin: Theme.of(context).cardTheme.margin,
              height: 90,
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    ElevatedButton(
                      onPressed: _closeDrawer,
                      child: Icon(Icons.close),
                    ),
                  ],
                ),
              ),
            ),
          ],
          //separatorBuilder: (BuildContext context, int index) => const Divider(),
        ),
      ),
      bottomNavigationBar: BottomAppBar(
        shape: const CircularNotchedRectangle(),
        child: Container(height: 30.0),
      ),
      floatingActionButton: FloatingActionButton(
        //onPressed: _incrementCounter,
        onPressed: () {
          Navigator.pushNamed(context, '/new');
        },
        tooltip: 'new recording',
        child: Icon(Icons.add),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}


lib/screens/audio_session.dart:

import 'package:flutter/material.dart';
import 'package:happy_recorder/models/audio_rec.dart';

class AudioSession extends StatefulWidget {
  final int arIndex;

  // do we need key?
  AudioSession({Key? key, required this.arIndex}) : super(key: key);
  //AudioSession({required this.arIndex});

  @override
  _AudioSessionState createState() => _AudioSessionState();
}

class _AudioSessionState extends State<AudioSession> {
  int _selectedBottomBarItemIndex = 0;

  void _onBottomBarItemTapped(int index) {
    setState(() {
      _selectedBottomBarItemIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).colorScheme.background,
      appBar: AppBar(
        title: Text("Record"),
        // automaticallyImplyLeading: false, // not display <- back btn
      ),
      body: Container(
        padding: const EdgeInsets.all(5.0),
        //color: Theme.of(context).scaffoldBackgroundColor,
        child:
          Column(
            children: [
              Hero(
                tag: 'audio_rec_${widget.arIndex}',
                child: Card(
                  child: ListTile(
                    title: Text(audioRec[widget.arIndex].title),
                    subtitle: Text(audioRec[widget.arIndex].description),
                  ),
                ),
              ),
              Container(
                width: double.infinity, // make it max width,
                padding: const EdgeInsets.all(10.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // Dart 2.3 以上才支援
                    if (_selectedBottomBarItemIndex != 0)
                      Text(
                        "Bottom bar $_selectedBottomBarItemIndex selected",
                        //style: TextStyle(fontFamily: 'JBMono', fontSize: 18),
                        style: Theme.of(context).textTheme.subtitle2,
                      )
                    else
                      Text(
                        "nothing selected",
                        style: Theme.of(context).textTheme.subtitle2,
                      ),
                    Text(' '),
                    Text(
                      'Audio info',
                      style: Theme.of(context).textTheme.headline5,
                    ),
                  ],
                ),
              ),
            ],
          ),
      ),
      // bottomNavigationBar 不使用 BottomNavigationBar() 是因為它的按鈕一定有一個預設是已選定
      bottomNavigationBar: Container(
        height: 110,
        padding: const EdgeInsets.only(top: 10.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Column(
              children: [
                Ink(
                  decoration: ShapeDecoration(
                    color: Theme.of(context).colorScheme.secondary,
                    //color: Colors.black,
                    shape: CircleBorder(),
                  ),
                  child: IconButton(
                    icon: const Icon(Icons.record_voice_over),
                    iconSize: 38,
                    color: Theme.of(context).bottomAppBarColor,
                    onPressed: () {
                      _onBottomBarItemTapped(1);
                    },
                  ),
                ),
                Text(
                  'record',
                  style: TextStyle(color: Theme.of(context).colorScheme.secondary.withOpacity(0.8)),
                ),
              ],
            ),
            Column(
              children: [
                Ink(
                  decoration: ShapeDecoration(
                    color: Theme.of(context).colorScheme.secondary,
                    shape: CircleBorder(),
                  ),
                  child: IconButton(
                    icon: const Icon(Icons.stop),
                    iconSize: 38,
                    color: Theme.of(context).bottomAppBarColor,
                    onPressed: () {
                      Navigator.pop(context);
                    },
                  ),
                ),
                Text(
                  'stop',
                  style: TextStyle(color: Theme.of(context).colorScheme.secondary.withOpacity(0.8)),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}


lib/screens/page_404.dart:

import 'package:flutter/material.dart';

class Page404 extends StatelessWidget {
  const Page404({Key? key, required this.routeName}) : super(key: key);

  final String? routeName;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Going to " + (routeName ?? "null") + "?"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Page Not found'),
            SizedBox(height: 5),
            Icon(
              Icons.sentiment_very_dissatisfied_outlined,
              color: Theme.of(context).colorScheme.error,
            ),
          ],
        )
      )
    );
  }
}


lib/models/audio_rec.dart:

class AudioRec {
  final String title;
  final String description;

  AudioRec(this.title, this.description);
}

// Generate test data
List audioRec = List.generate( 20, (i) => AudioRec(
    'Audio $i',
    'An audio description for audio $i title',
  ),
);


lib/blocs/my_theme_mode.dart:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class MyThemeMode with ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.system; // must be initial

  //MyThemeMode(){
  //  notifyListeners();
  //}

  // getter
  ThemeMode get getMode {
    return _themeMode;
  }

  void setModeTo(ThemeMode vMode) async {
    _themeMode = vMode;
    notifyListeners();
  }
}





WriterShelf™ is a unique multiple pen name blogging and forum platform. Protect relationships and your privacy. Take your writing in new directions. ** Join WriterShelf**
WriterShelf™ is an open writing platform. The views, information and opinions in this article are those of the author.


Article info

This article is part of:
Categories:
Tags:
Total: 1223 words


Share this article:
About the Author

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




Join the discussion now!
Don't wait! Sign up to join the discussion.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.