flutterchina / azlistview

A Flutter sticky headers & index ListView. Flutter 城市列表、联系人列表,索引&悬停。
BSD 3-Clause "New" or "Revised" License
1.14k stars 282 forks source link

Scrolling using Shortcuts #43

Open RealDanCarroll opened 3 years ago

RealDanCarroll commented 3 years ago

Hello, I have attempted to create a version of the listview that is similar to the Github Languages example. I have successfully implemented all needed features except 1. Scrolling to a specific position based on clicking on the shortcut list does not work. It doesn't attempt to scroll. What functions control the scroll? What are the necessary dependencies? (There is no error message when I click) ((The Letter Preview shows up when I click, but the specific letter also does not remain highlighted))

Sky24n commented 3 years ago

Can you provide your code? Let me analyze the reasons.

RealDanCarroll commented 3 years ago

Contents of abc_list.dart

`import 'package:flutter/material.dart'; import 'package:azlistview/azlistview.dart';

import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../classes/singleUserData.dart';

class AbcList extends StatefulWidget { const AbcList({ Key key, this.userList, }) : super(key: key); final List userList;

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

class _AbcListState extends State { final ItemScrollController itemScrollController = ItemScrollController(); List originList = List(); List dataList = List();

TextEditingController textEditingController;

@override void initState() { super.initState(); textEditingController = TextEditingController(); tagData(); }

@override void dispose() { textEditingController.dispose(); super.dispose(); }

void tagData() async { originList = widget.userList.map((v) { SingleUserData thisUser = SingleUserData.fromJson(v.toJson()); String tag = thisUser.lastName.substring(0, 1).toUpperCase();

  if (RegExp("[A-Z]").hasMatch(tag)) {
    thisUser.tagIndex = tag;
  } else {
    thisUser.tagIndex = "#";
  }

  return thisUser;
}).toList();
_handleList(originList);

}

static bool isEmpty(Object object) { if (object == null) return true; if (object is String && object.isEmpty) { return true; } else if (object is Iterable && object.isEmpty) { return true; } else if (object is Map && object.isEmpty) { return true; } return false; }

void _handleList(List list) { dataList.clear(); if (isEmpty(list)) { setState(() {}); return; } dataList.addAll(list);

// A-Z sort.
SuspensionUtil.sortListBySuspensionTag(dataList);
// show sus tag.
SuspensionUtil.setShowSuspensionStatus(dataList);

setState(() {});

if (itemScrollController.isAttached) {
  print('Its Attached');
  itemScrollController.jumpTo(index: 0);
}

}

Widget getSusItem(BuildContext context, String tag, {double susHeight = 40}) { return Container( height: susHeight, width: MediaQuery.of(context).size.width, padding: EdgeInsets.only(left: 16.0), color: Color(0xFFF3F4F5), alignment: Alignment.centerLeft, child: Text( '$tag', softWrap: false, style: TextStyle( fontSize: 14.0, color: Color(0xFF666666), ), ), ); }

Widget getListItem(BuildContext context, SingleUserData model, {double susHeight = 40}) { return ListTile( title: Text(model.lastName + model.firstName), onTap: () { print(model.lastName); // LogUtil.v("onItemClick : $model"); // Utils.showSnackBar(context, "onItemClick : $model"); }, ); }

void _search(String text) { if (isEmpty(text)) { _handleList(originList); } else { List list = originList.where((v) { var lastNameSearch = v.lastName.toLowerCase().contains(text.toLowerCase()); var firstNameSearch = v.firstName.toLowerCase().contains(text.toLowerCase()); if (lastNameSearch == true || firstNameSearch == true) { return true; } else { return false; } }).toList(); _handleList(list); } }

@override Widget build(BuildContext context) { // print(dataList); return Expanded( child: Column( children: [ Container( margin: EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all( color: Color.fromARGB(255, 225, 226, 230), width: 0.33), color: Color.fromARGB(255, 239, 240, 244), borderRadius: BorderRadius.circular(12)), child: TextField( autofocus: false, onChanged: (value) { _search(value); }, controller: textEditingController, decoration: InputDecoration( prefixIcon: Icon( Icons.search, color: Colors.grey[300], ), suffixIcon: Offstage( offstage: textEditingController.text.isEmpty, child: InkWell( onTap: () { textEditingController.clear(); _search(''); }, child: Icon( Icons.cancel, color: Colors.grey[300], ), ), ), border: InputBorder.none, hintText: 'Search', hintStyle: TextStyle(color: Colors.grey[300])), ), ), Expanded( child: AzListView( data: dataList, physics: AlwaysScrollableScrollPhysics(), itemCount: dataList.length, itemBuilder: (BuildContext context, int index) { SingleUserData model = dataList[index]; return getListItem(context, model); }, itemScrollController: itemScrollController, susItemBuilder: (BuildContext context, int index) { SingleUserData model = dataList[index]; return getSusItem(context, model.getSuspensionTag()); }, indexBarOptions: IndexBarOptions( needRebuild: true, selectTextStyle: TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500), selectItemDecoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF333333)), indexHintWidth: 96, indexHintHeight: 97, indexHintDecoration: BoxDecoration( image: DecorationImage( image: AssetImage( 'lib/assets/images/ic_index_bar_bubble_white.png'), fit: BoxFit.contain, ), ), indexHintAlignment: Alignment.centerRight, indexHintTextStyle: TextStyle(fontSize: 24.0, color: Colors.black87), indexHintOffset: Offset(-30, 0), ), ), ) ], ), ); } } `

Contents of singleUserData.dart

`import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:azlistview/azlistview.dart';

class SingleUserData with ISuspensionBean { String firstName; String lastName; String screeningStatus; String screeningDate; String tagIndex;

SingleUserData( {this.firstName, this.lastName, this.screeningStatus, this.screeningDate, this.tagIndex});

SingleUserData.fromJson(Map<String, dynamic> json) : firstName = json['firstName'], lastName = json['lastName'], screeningStatus = json['screeningStatus'], screeningDate = json['screeningDate'].toString(), tagIndex = json['tagIndex'];

Map<String, dynamic> toJson() { final Map<String, dynamic> map = <String, dynamic>{}; void addIfNonNull(String fieldName, dynamic value) { if (value != null) { map[fieldName] = value; } }

// print(tagIndex);
addIfNonNull('lastName', lastName);
addIfNonNull('firstName', firstName);
addIfNonNull('screeningStatus', screeningStatus);
addIfNonNull('screeningDate', screeningDate);
addIfNonNull('tagIndex', tagIndex);
return map;

}

@override String getSuspensionTag() => lastName;

@override String toString() => json.encode(this); } ` Contents of user_screening_list_screen.dart

`import 'package:Varify/widgets/abc_list.dart'; import '../classes/singleUserData.dart'; import 'package:flutter/material.dart'; import '../widgets/navigation_arguments.dart'; import '../widgets/big_button.dart'; import '../providers/student.dart'; import 'add_new_student_view.dart'; import 'edit_student_view.dart'; import 'package:Varify/getit.dart'; import 'package:Varify/services/auth.dart'; import 'package:Varify/services/db.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'dart:async'; import '../widgets/screening_status_indicator_light.dart';

var auth = getIt.get(); var db = getIt.get();

List _unsortedUsers; List _sortedUsers = []; List _normalList = []; String _thisUserType; bool _showAddButton = false;

class UserListView extends StatefulWidget { static const routeName = '/UserList'; final String userType;

UserListView({Key key, @required this.userType}) : super(key: key);

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

class _UserListViewState extends State with RouteAware { StreamSubscription authState; bool _gettingList = false; List userListeners = [];

@override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context)); }

@override void dispose() { print("disposed user list"); userListeners.forEach((element) { element.cancel(); }); userListeners.clear(); if (authState != null) authState.cancel(); // reset(); routeObserver.unsubscribe(this);

super.dispose();

}

@override void didPopNext() { // reset(); _getUserList(); super.didPopNext(); }

reset() { _unsortedUsers = null; _sortedUsers = []; _normalList = []; _thisUserType = null; _showAddButton = false; }

@override void initState() { reset(); if (authState != null) return; authState = auth.fb.authStateChanges().listen((User userData) { if (userData == null) { auth.user = null; setState(() => {}); return; } else { print('User is signed in!'); db.getStaff(userData.uid).then((staff) { staff['email'] = userData.email; staff['id'] = userData.uid; auth.user = staff; setState(() => {}); }); } });

super.initState();

}

_getUserList() async { _gettingList = true; print("list type: " + widget.userType); if (widget.userType == 'Student') { print("is teacher:" + auth.user['staffType'] == "Teacher"); if (auth.user['staffType'] == "Teacher") { _unsortedUsers = await db.getTeacherStudents(auth.user['id']); _showAddButton = false; } else { _showAddButton = true; _unsortedUsers = await db.getAllStudents(); } ; _thisUserType = 'Student'; } else { _showAddButton = true; _unsortedUsers = await db.getAllStaff(); _thisUserType = 'Staff Member'; } if (_unsortedUsers == null) return;

userListeners.forEach((element) {
  element.cancel();
});
userListeners.clear();

_unsortedUsers.forEach((el) {
  // print(el.toString());
  StreamSubscription userListen =
      db.subscribetoStudentResponses(el['id']).listen((event) {
    // print(event.toString());
    if (event is! Map) return;
    int index = _unsortedUsers
        .indexWhere((student) => student['id'] == event['userID']);
    if (index == null) return;
    _unsortedUsers[index]['lastResponse'] = event;
    sortStudents();
  });
  userListeners.add(userListen);
});
_gettingList = false;
sortStudents();

}

sortStudents() { _sortedUsers = _unsortedUsers; _sortedUsers.sort((a, b) => a['firstName'].toLowerCase().compareTo(b['firstName'].toLowerCase())); _sortedUsers.sort((a, b) => a['lastName'].toLowerCase().compareTo(b['lastName'].toLowerCase())); setState(() => {}); }

@override Widget build(BuildContext context) { List users = []; users.addAll(_sortedUsers); _normalList = [];

users.forEach((user) {
  List response = [];
  if (!user.containsKey('lastResponse')) {
    response = ['noResponse', 'noResponse'];
  } else {
    response = [
      user['lastResponse']['screeningStatus'].toString(),
      user['lastResponse']['submissionTime'].toString()
    ];
  }
  _normalList.add(SingleUserData(
    firstName: user['firstName'],
    lastName: user['lastName'],
    screeningStatus: response[0],
    screeningDate: response[1],
  ));
});

if (_unsortedUsers == null && auth.user != null && !_gettingList)
  _getUserList();
return _unsortedUsers == null
    ? Center(
        child: Container(
          width: 250,
          height: 250,
          child: CircularProgressIndicator(
            backgroundColor: Colors.white,
            valueColor: new AlwaysStoppedAnimation<Color>(Colors.blue),
            strokeWidth: 15.0,
          ),
        ),
      )
    : Scaffold(
        appBar: AppBar(
          title: Text('Varify'),
        ),
        body: Align(
          alignment: Alignment.topCenter,
          child: Container(
              constraints: BoxConstraints(minWidth: 350, maxWidth: 800),
              width: MediaQuery.of(context).size.width * .9,
              child: auth.user != null
                  ? body(context)
                  : Text("Requires Admin Login")),
        ),
      );

} }

List filteredList(context) { List users = []; users.addAll(_sortedUsers); _normalList = [];

users.forEach((user) { List response = []; if (!user.containsKey('lastResponse')) { response = null; } else { response = [ user['lastResponse']['screeningStatus'].toString(), user['lastResponse']['submissionTime'] ]; } _normalList.add(SingleUserData( firstName: user['firstName'], lastName: user['lastName'], screeningStatus: response[0], screeningDate: response[1], )); });

return _normalList; }

Widget body(BuildContext context) { return Column( children: [ SizedBox( height: 15, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: Text( 'Click on any user\'s name to view their most recent screening responses. Click Edit to modify the user\'s data.', style: Theme.of(context).textTheme.bodyText1, ), ), SizedBox( height: 15, ), _thisUserType != "Staff Member" ? Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: _showAddButton ? BigButton('Add New User', () { Navigator.of(context) .pushNamed(AddStudentScreen.routeName); }) : SizedBox(), ) : SizedBox(), AbcList(userList: _normalList), ], ); } `

Sky24n commented 3 years ago

It should be a problem with your code that caused scrollable_positioned_list to not work! The exact reason is unknown because your code cannot be run.But I tested abclist, and it works. suggest: Data should not be requested in the build.

RealDanCarroll commented 3 years ago

Is there a better way I can provide the code?

When you say “data should not be requested in the build”, can you elaborate?

On Dec 17, 2020, at 12:55 AM, Sky24n notifications@github.com wrote:

 It should be a problem with your code that caused scrollable_positioned_list to not work! The exact reason is unknown because your code cannot be run.But I tested abclist, and it works. suggest: Data should not be requested in the build.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

Sky24n commented 3 years ago

Create a simple demo project and make sure it runs. and use test data.

db.getAllStudents();
 replace
Future getAllStudents() {
    List<Student> list = [
      Student(),
      Student(),
      Student(),
      Student(),
      ...
    ];
    return Future.delayed(Duration(seconds: 1), () {
      return list;
    });
  }

My email: 863764940@qq.com

RealDanCarroll commented 3 years ago

I created a simple demo project using static data, it works great.

I used the same static data in my project, I have the same issue.

There must be some other conflict with my project or with the way I am attempting to implement this.

I emailed you to discuss further.