diff --git a/lib/home.dart b/lib/home.dart index 658d164..316fa1a 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -6,6 +6,7 @@ import 'package:git_touch/models/theme.dart'; import 'package:git_touch/screens/bb_explore.dart'; import 'package:git_touch/screens/bb_teams.dart'; import 'package:git_touch/screens/bb_user.dart'; +import 'package:git_touch/screens/gl_search.dart'; import 'package:git_touch/screens/gt_orgs.dart'; import 'package:git_touch/screens/gt_user.dart'; import 'package:git_touch/screens/gl_explore.dart'; @@ -62,6 +63,8 @@ class _HomeState extends State { case 1: return GlGroupsScreenn(); case 2: + return GlSearchScreen(); + case 3: return GlUserScreen(null); } break; @@ -144,6 +147,10 @@ class _HomeState extends State { icon: Icon(Icons.group), title: Text('Groups'), ), + BottomNavigationBarItem( + icon: Icon(Icons.search), + title: Text('Search'), + ), BottomNavigationBarItem( icon: Icon(Icons.person), title: Text('Me'), diff --git a/lib/screens/gl_search.dart b/lib/screens/gl_search.dart new file mode 100644 index 0000000..9023286 --- /dev/null +++ b/lib/screens/gl_search.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:git_touch/models/theme.dart'; +import 'package:git_touch/scaffolds/common.dart'; +import 'package:git_touch/utils/utils.dart'; +import 'package:git_touch/widgets/loading.dart'; +import 'package:git_touch/widgets/user_item.dart'; +import 'package:primer/primer.dart'; +import 'package:provider/provider.dart'; +import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/widgets/repository_item.dart'; +import 'package:timeago/timeago.dart' as timeago; +import 'package:git_touch/models/gitlab.dart'; + +class GlSearchScreen extends StatefulWidget { + @override + _GlSearchScreenState createState() => _GlSearchScreenState(); +} + +class _GlSearchScreenState extends State { + int _activeTab = 0; + bool _loading = false; + List _projects = List(); + List _users = List(); + + TextEditingController _controller; + + String get _keyword => _controller.text?.trim() ?? ''; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Future _query() async { + if (_loading || _keyword.isEmpty) return; + + var keyword = _controller.text; + setState(() { + _loading = true; + }); + try { + final projects = await Provider.of(context) + .fetchGitlabWithPage('/search?scope=projects&search=$keyword'); + final users = await Provider.of(context) + .fetchGitlabWithPage('/search?scope=users&search=$keyword'); + _projects = [for (var v in projects.data) GitlabProject.fromJson(v)]; + _users = [for (var v in users.data) GitlabUser.fromJson(v)]; + } finally { + setState(() { + _loading = false; + }); + } + } + + Widget _buildInput() { + final theme = Provider.of(context); + switch (Provider.of(context).theme) { + case AppThemeType.cupertino: + return Container( + color: theme.palette.background, + child: CupertinoTextField( + prefix: Row( + children: [ + SizedBox(width: 8), + Icon(Octicons.search, size: 20, color: PrimerColors.gray400), + ], + ), + placeholder: 'Search', + clearButtonMode: OverlayVisibilityMode.editing, + textInputAction: TextInputAction.go, + onSubmitted: (_) => _query(), + controller: _controller, + ), + ); + default: + return TextField( + decoration: InputDecoration.collapsed(hintText: 'Search'), + textInputAction: TextInputAction.go, + onSubmitted: (_) => _query(), + controller: _controller, + ); + } + } + + _onTabSwitch(int index) { + setState(() { + _activeTab = index; + }); + if (_projects.isEmpty || _users.isEmpty) { + _query(); + } + } + + static const tabs = ['Projects', 'Users']; + + Widget _buildItem(p) { + switch (_activeTab) { + case 0: + final updatedAt = timeago.format(p.lastActivityAt); + return RepositoryItem.gl( + payload: p, + note: 'Updated $updatedAt', + ); + case 1: + return UserItem( + login: p.username, + url: '/gitlab/user/${p.id}', + avatarUrl: p.avatarUrl, + bio: Text(p.bio ?? ''), + ); + } + } + + @override + Widget build(BuildContext context) { + final theme = Provider.of(context).theme; + + final scaffold = CommonScaffold( + title: _buildInput(), + body: SingleChildScrollView( + child: Column( + children: [ + if (theme == AppThemeType.cupertino) + Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: CupertinoSlidingSegmentedControl( + groupValue: _activeTab, + onValueChanged: _onTabSwitch, + children: tabs.asMap().map((key, text) => MapEntry( + key, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text(text, style: TextStyle(fontSize: 14)), + ))), + ), + ), + ), + if (_loading) + Loading() + else if (_activeTab == 0) + ..._projects.map(_buildItem).toList() + else + ..._users.map(_buildItem).toList(), + ], + ), + ), + bottom: TabBar( + onTap: _onTabSwitch, + tabs: tabs.map((text) => Tab(text: text.toUpperCase())).toList(), + ), + ); + + if (theme == AppThemeType.material) { + return DefaultTabController( + length: tabs.length, + child: scaffold, + ); + } else { + return scaffold; + } + } +} diff --git a/lib/widgets/repository_item.dart b/lib/widgets/repository_item.dart index d73c2af..3d6acc5 100644 --- a/lib/widgets/repository_item.dart +++ b/lib/widgets/repository_item.dart @@ -68,7 +68,7 @@ class RepositoryItem extends StatelessWidget { url = '/gitlab/projects/${payload.id}', avatarLink = payload.namespace.kind == 'group' ? '/gitlab/group/${payload.namespace.id}' - : '/gitlab/user/${payload.owner.id}', + : '/gitlab/user/${payload.namespace.id}', iconData = _buildGlIconData(payload.visibility); RepositoryItem.gh({ diff --git a/pubspec.yaml b/pubspec.yaml index 66833ad..c365279 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: flutter_markdown: ^0.3.3 shared_preferences: ^0.5.6 share: ^0.6.0 - flutter_svg: ^0.13.0 + flutter_svg: ^0.17.4 launch_review: ^2.0.0 timeago: ^2.0.18 provider: ^3.1.0