diff --git a/lib/main.dart b/lib/main.dart index 1e3b8c2..f2386ec 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:git_touch/models/code.dart'; import 'package:git_touch/models/settings.dart'; import 'package:git_touch/models/theme.dart'; import 'package:git_touch/screens/issues.dart'; @@ -34,6 +35,7 @@ class _HomeState extends State { // FIXME: Provider.of(context).init(); Provider.of(context).init(); + Provider.of(context).init(); }); } @@ -134,7 +136,7 @@ class _HomeState extends State { } switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return CupertinoApp( home: CupertinoTheme( data: CupertinoThemeData( @@ -181,6 +183,7 @@ class App extends StatelessWidget { ChangeNotifierProvider(builder: (context) => NotificationModel()), ChangeNotifierProvider(builder: (context) => ThemeModel()), ChangeNotifierProvider(builder: (context) => SettingsModel()), + ChangeNotifierProvider(builder: (context) => CodeModel()), ], child: Home(), ); diff --git a/lib/models/code.dart b/lib/models/code.dart new file mode 100644 index 0000000..558a59d --- /dev/null +++ b/lib/models/code.dart @@ -0,0 +1,71 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_highlight/theme_map.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class CodeModel with ChangeNotifier { + static const _kTheme = 'code-theme'; + static const _kFontSize = 'code-font-size'; + static const _kFontFamily = 'code-font-family'; + + static var themes = themeMap.keys.toList(); + static const fontSizes = [12, 13, 14, 15, 16, 17, 18, 19, 20]; + static const fontFamilies = ['System']; + + String _theme = 'github'; + int _fontSize = 14; + String _fontFamily = 'System'; + + String get theme => _theme; + int get fontSize => _fontSize; + String get fontFamily => _fontFamily; + + init() async { + var prefs = await SharedPreferences.getInstance(); + var vh = prefs.getString(_kTheme); + var vs = prefs.getInt(_kFontSize); + var vf = prefs.getString(_kFontFamily); + + print('read code: $vh, $vs, $vf'); + if (themeMap.keys.contains(vh)) { + _theme = vh; + } + if (fontSizes.contains(vs)) { + _fontSize = vs; + } + if (fontFamilies.contains(vf)) { + _fontFamily = vf; + } + + notifyListeners(); + } + + setTheme(String v) async { + var prefs = await SharedPreferences.getInstance(); + + await prefs.setString(_kTheme, v); + print('write code theme: $v'); + + _theme = v; + notifyListeners(); + } + + setFontSize(int v) async { + var prefs = await SharedPreferences.getInstance(); + + await prefs.setInt(_kFontSize, v); + print('write code font size: $v'); + + _fontSize = v; + notifyListeners(); + } + + setFontFamily(String v) async { + var prefs = await SharedPreferences.getInstance(); + + await prefs.setString(_kFontFamily, v); + print('write code font family: $v'); + + _fontFamily = v; + notifyListeners(); + } +} diff --git a/lib/models/theme.dart b/lib/models/theme.dart index 8338750..fc869db 100644 --- a/lib/models/theme.dart +++ b/lib/models/theme.dart @@ -10,10 +10,10 @@ class DialogOption { DialogOption({this.value, this.widget}); } -class ThemeMap { +class AppThemeMap { static const material = 0; static const cupertino = 1; - static const values = [ThemeMap.material, ThemeMap.cupertino]; + static const values = [AppThemeMap.material, AppThemeMap.cupertino]; } class ThemeModel with ChangeNotifier { @@ -28,12 +28,12 @@ class ThemeModel with ChangeNotifier { int v = prefs.getInt(storageKey); print('read theme: $v'); - if (ThemeMap.values.contains(v)) { + if (AppThemeMap.values.contains(v)) { _theme = v; } else if (Platform.isIOS) { - _theme = ThemeMap.cupertino; + _theme = AppThemeMap.cupertino; } else { - _theme = ThemeMap.material; + _theme = AppThemeMap.material; } notifyListeners(); @@ -55,7 +55,7 @@ class ThemeModel with ChangeNotifier { bool fullscreenDialog = false, }) { switch (theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: Navigator.of(context).push(CupertinoPageRoute( builder: builder, fullscreenDialog: fullscreenDialog, @@ -71,7 +71,7 @@ class ThemeModel with ChangeNotifier { Future showConfirm(BuildContext context, String text) { switch (theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return showCupertinoDialog( context: context, builder: (context) { @@ -130,7 +130,7 @@ class ThemeModel with ChangeNotifier { var cancelWidget = Text('Cancel'); switch (theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return showCupertinoDialog( context: context, builder: (BuildContext context) { @@ -181,4 +181,38 @@ class ThemeModel with ChangeNotifier { ); } } + + Future showPicker( + BuildContext context, { + @required int initialItem, + @required List children, + @required Function(int) onSelectedItemChanged, + }) { + switch (theme) { + case AppThemeMap.cupertino: + return showCupertinoModalPopup( + context: context, + builder: (context) { + return Container( + height: 300, + child: CupertinoPicker( + backgroundColor: CupertinoColors.white, + children: children, + itemExtent: 40, + scrollController: + FixedExtentScrollController(initialItem: initialItem), + onSelectedItemChanged: onSelectedItemChanged, + ), + ); + }, + ); + default: + return showModalBottomSheet( + context: context, + builder: (context) { + return null; // TODO: + }, + ); + } + } } diff --git a/lib/scaffolds/list.dart b/lib/scaffolds/list.dart index eb65a52..e3e8a08 100644 --- a/lib/scaffolds/list.dart +++ b/lib/scaffolds/list.dart @@ -184,7 +184,7 @@ class _ListScaffoldState extends State> { @override Widget build(BuildContext context) { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: List slivers = [ CupertinoSliverRefreshControl(onRefresh: _refresh) ]; diff --git a/lib/scaffolds/long_list.dart b/lib/scaffolds/long_list.dart index d90d3d7..df98502 100644 --- a/lib/scaffolds/long_list.dart +++ b/lib/scaffolds/long_list.dart @@ -181,7 +181,7 @@ class _LongListScaffoldState extends State> { @override Widget build(BuildContext context) { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: List slivers = [ CupertinoSliverRefreshControl(onRefresh: _refresh) ]; diff --git a/lib/scaffolds/refresh.dart b/lib/scaffolds/refresh.dart index 6b175bc..2bf2022 100644 --- a/lib/scaffolds/refresh.dart +++ b/lib/scaffolds/refresh.dart @@ -80,7 +80,7 @@ class _RefreshScaffoldState extends State> { @override Widget build(BuildContext context) { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: widget.title, diff --git a/lib/scaffolds/refresh_stateless.dart b/lib/scaffolds/refresh_stateless.dart index 7840116..7e214e8 100644 --- a/lib/scaffolds/refresh_stateless.dart +++ b/lib/scaffolds/refresh_stateless.dart @@ -41,7 +41,7 @@ class RefreshStatelessScaffold extends StatelessWidget { @override Widget build(BuildContext context) { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar(middle: title, trailing: trailing), diff --git a/lib/scaffolds/simple.dart b/lib/scaffolds/simple.dart index 3f1ef5d..0c7861b 100644 --- a/lib/scaffolds/simple.dart +++ b/lib/scaffolds/simple.dart @@ -22,7 +22,7 @@ class SimpleScaffold extends StatelessWidget { @override Widget build(BuildContext context) { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar(middle: title, trailing: trailing), @@ -32,11 +32,7 @@ class SimpleScaffold extends StatelessWidget { ); default: return Scaffold( - appBar: AppBar( - title: title, - actions: actions, - bottom: bottom, - ), + appBar: AppBar(title: title, actions: actions, bottom: bottom), body: SingleChildScrollView(child: bodyBuilder()), ); } diff --git a/lib/screens/code_settings.dart b/lib/screens/code_settings.dart new file mode 100644 index 0000000..7f8e07b --- /dev/null +++ b/lib/screens/code_settings.dart @@ -0,0 +1,105 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_highlight/flutter_highlight.dart'; +import 'package:flutter_highlight/theme_map.dart'; +import 'package:git_touch/models/code.dart'; +import 'package:git_touch/models/theme.dart'; +import 'package:git_touch/scaffolds/simple.dart'; +import 'package:git_touch/utils/utils.dart'; +import 'package:git_touch/widgets/app_bar_title.dart'; +import 'package:git_touch/widgets/table_view.dart'; +import 'package:provider/provider.dart'; + +class CodeSettingsScreen extends StatelessWidget { + final String code; + final String language; + + CodeSettingsScreen(this.code, this.language); + + static Timer _themeDebounce; + + @override + Widget build(BuildContext context) { + var codeProvider = Provider.of(context); + + return SimpleScaffold( + title: AppBarTitle('Code theme'), + bodyBuilder: () { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TableView( + items: [ + TableViewItem( + text: Text('Syntax Highlighting'), + rightWidget: Text(codeProvider.theme), + onTap: () { + Provider.of(context).showPicker( + context, + children: CodeModel.themes.map((k) => Text(k)).toList(), + initialItem: + CodeModel.themes.indexOf(codeProvider.theme), + onSelectedItemChanged: (int value) { + if (_themeDebounce?.isActive ?? false) + _themeDebounce.cancel(); + _themeDebounce = + Timer(const Duration(milliseconds: 500), () { + Provider.of(context) + .setTheme(CodeModel.themes[value]); + }); + }, + ); + }), + TableViewItem( + text: Text('Font Size'), + rightWidget: Text(codeProvider.fontSize.toString()), + onTap: () { + Provider.of(context).showPicker( + context, + children: CodeModel.fontSizes + .map((k) => Text(k.toString())) + .toList(), + initialItem: + CodeModel.fontSizes.indexOf(codeProvider.fontSize), + onSelectedItemChanged: (int value) { + if (_themeDebounce?.isActive ?? false) + _themeDebounce.cancel(); + _themeDebounce = + Timer(const Duration(milliseconds: 500), () { + Provider.of(context) + .setFontSize(CodeModel.fontSizes[value]); + }); + }, + ); + }, + ), + TableViewItem( + text: Text('Font Family'), + rightWidget: Text(codeProvider.fontFamily.toString()), + onTap: () { + // TODO: + }, + ), + ], + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: HighlightView( + code, + language: language, + theme: themeMap[codeProvider.theme], + textStyle: TextStyle( + fontSize: codeProvider.fontSize.toDouble(), + fontFamily: monospaceFont, + ), + padding: const EdgeInsets.all(10), + ), + ) + ], + ); + }, + ); + } +} diff --git a/lib/screens/notifications.dart b/lib/screens/notifications.dart index 8120135..80fdf78 100644 --- a/lib/screens/notifications.dart +++ b/lib/screens/notifications.dart @@ -184,7 +184,7 @@ $key: pullRequest(number: ${item.number}) { Widget _buildTitle() { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: // var textStyle = DefaultTextStyle.of(context).style; return DefaultTextStyle( style: TextStyle(fontSize: 16), diff --git a/lib/screens/object.dart b/lib/screens/object.dart index e28a1af..bf47c94 100644 --- a/lib/screens/object.dart +++ b/lib/screens/object.dart @@ -1,6 +1,10 @@ -import 'package:flutter_highlight/themes/github.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_highlight/theme_map.dart'; +import 'package:git_touch/models/code.dart'; +import 'package:git_touch/screens/code_settings.dart'; import 'package:git_touch/screens/image_view.dart'; import 'package:git_touch/widgets/app_bar_title.dart'; +import 'package:git_touch/widgets/link.dart'; import 'package:git_touch/widgets/markdown_view.dart'; import 'package:git_touch/widgets/table_view.dart'; import 'package:path/path.dart' as path; @@ -10,7 +14,6 @@ import 'package:git_touch/models/settings.dart'; import 'package:provider/provider.dart'; import 'package:git_touch/scaffolds/refresh.dart'; import 'package:git_touch/utils/utils.dart'; -import 'package:git_touch/widgets/link.dart'; import 'package:primer/primer.dart'; import 'package:seti/seti.dart'; @@ -29,14 +32,16 @@ class ObjectScreen extends StatelessWidget { this.type = 'tree', }); - String get expression => '$branch:' + paths.join('/'); - String get extname { + String get _expression => '$branch:' + paths.join('/'); + String get _extname { if (paths.isEmpty) return ''; var dotext = path.extension(paths.last); if (dotext.isEmpty) return ''; return dotext.substring(1); } + String get _language => _extname.isEmpty ? 'plaintext' : _extname; + String get rawUrl => 'https://raw.githubusercontent.com/$owner/$name/$branch/' + paths.join('/'); // TODO: @@ -111,8 +116,9 @@ class ObjectScreen extends StatelessWidget { ); } - Widget _buildBlob(payload) { - switch (extname) { + Widget _buildBlob(BuildContext context, payload) { + var codeProvider = Provider.of(context); + switch (_extname) { case 'md': case 'markdown': return Padding( @@ -124,10 +130,12 @@ class ObjectScreen extends StatelessWidget { scrollDirection: Axis.horizontal, child: HighlightView( payload['text'], - language: extname.isEmpty ? 'plaintext' : extname, - theme: githubTheme, + language: _language, + theme: themeMap[codeProvider.theme], padding: EdgeInsets.all(10), - textStyle: TextStyle(fontFamily: monospaceFont), + textStyle: TextStyle( + fontSize: codeProvider.fontSize.toDouble(), + fontFamily: monospaceFont), ), ); } @@ -140,7 +148,7 @@ class ObjectScreen extends StatelessWidget { onRefresh: () async { var data = await Provider.of(context).query('''{ repository(owner: "$owner", name: "$name") { - object(expression: "$expression") { + object(expression: "$_expression") { $_subQuery } } @@ -161,12 +169,25 @@ class ObjectScreen extends StatelessWidget { return data['repository']['object']; }, + trailingBuilder: (payload) { + switch (type) { + case 'blob': + return Link( + child: Icon(Octicons.settings, size: 20), + material: false, + screenBuilder: (_) => + CodeSettingsScreen(payload['text'], _language), + ); + default: + return null; + } + }, bodyBuilder: (payload) { switch (type) { case 'tree': return _buildTree(payload); case 'blob': - return _buildBlob(payload); + return _buildBlob(context, payload); default: return null; } diff --git a/lib/screens/search.dart b/lib/screens/search.dart index 345d360..16ef9f6 100644 --- a/lib/screens/search.dart +++ b/lib/screens/search.dart @@ -46,7 +46,7 @@ class _SearchScreenState extends State { Widget _buildInput() { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return CupertinoTextField( // padding: EdgeInsets.all(10), placeholder: 'Type to search', diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index f26d008..e0569c0 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -37,11 +37,11 @@ class SettingsScreen extends StatelessWidget { TableView(headerText: 'THEME', items: [ TableViewItem( text: Text('Material'), - rightWidget: - _buildRightWidget(themeProvider.theme == ThemeMap.material), + rightWidget: _buildRightWidget( + themeProvider.theme == AppThemeMap.material), onTap: () { - if (themeProvider.theme != ThemeMap.material) { - themeProvider.setTheme(ThemeMap.material); + if (themeProvider.theme != AppThemeMap.material) { + themeProvider.setTheme(AppThemeMap.material); } }, hideRightChevron: true, @@ -49,10 +49,10 @@ class SettingsScreen extends StatelessWidget { TableViewItem( text: Text('Cupertino'), rightWidget: _buildRightWidget( - themeProvider.theme == ThemeMap.cupertino), + themeProvider.theme == AppThemeMap.cupertino), onTap: () { - if (themeProvider.theme != ThemeMap.cupertino) { - themeProvider.setTheme(ThemeMap.cupertino); + if (themeProvider.theme != AppThemeMap.cupertino) { + themeProvider.setTheme(AppThemeMap.cupertino); } }, hideRightChevron: true, diff --git a/lib/screens/user.dart b/lib/screens/user.dart index 84f7944..e1d3811 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -148,7 +148,7 @@ class UserScreen extends StatelessWidget { var payload = data[0]; if (isMe) { return Link( - child: Icon(Icons.settings, size: 24), + child: Icon(Icons.settings), screenBuilder: (_) => SettingsScreen(), material: false, fullscreenDialog: true, diff --git a/lib/widgets/action.dart b/lib/widgets/action.dart index a28ade3..0aac5be 100644 --- a/lib/widgets/action.dart +++ b/lib/widgets/action.dart @@ -30,7 +30,7 @@ class ActionButton extends StatelessWidget { @override Widget build(BuildContext context) { switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return GestureDetector( child: Icon(iconData, size: 24), onTap: () async { diff --git a/lib/widgets/link.dart b/lib/widgets/link.dart index 92a5f0c..7967418 100644 --- a/lib/widgets/link.dart +++ b/lib/widgets/link.dart @@ -66,7 +66,8 @@ class Link extends StatelessWidget { color: bgColor ?? Colors.white, child: InkWell( child: child, - splashColor: theme == ThemeMap.cupertino ? Colors.transparent : null, + splashColor: + theme == AppThemeMap.cupertino ? Colors.transparent : null, onTap: () => _onTap(context, theme), ), ), diff --git a/lib/widgets/loading.dart b/lib/widgets/loading.dart index 2b4365d..3d829c1 100644 --- a/lib/widgets/loading.dart +++ b/lib/widgets/loading.dart @@ -12,7 +12,7 @@ class Loading extends StatelessWidget { // return Image.asset('images/loading.webp'); switch (Provider.of(context).theme) { - case ThemeMap.cupertino: + case AppThemeMap.cupertino: return CupertinoActivityIndicator(radius: 12); default: return Center( diff --git a/pubspec.yaml b/pubspec.yaml index fc750d3..196898b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: flutter: sdk: flutter http: ^0.12.0 - rxdart: ^0.20.0 + rxdart: ^0.22.2 uri: ^0.11.3 intl: ^0.15.7 url_launcher: ^5.0.2