0

I'm making a e-book application and user can draw lines on the WorkbookDrawingPage Widget by Stack layer.

I want to make the drawn lines saved to the device and books by using shared preference. I tried to use a Json encoding, but couldn't apply to my code. How to save List<Object> to SharedPreferences in Flutter?

Here's my code

WorkbookViewPage ( PdfViewer and DrawingPage widget)

class WorkbookViewPage extends StatefulWidget {
  String downloadedURL;
  String workbookName;

  WorkbookViewPage(this.downloadedURL, this.workbookName, {super.key});

  @override
  State<WorkbookViewPage> createState() => _WorkbookViewPageState();
}

class _WorkbookViewPageState extends State<WorkbookViewPage> {
  /// Widget Key
  GlobalKey _viewKey = GlobalKey();
  final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();

  /// Controller
  PdfViewerController _pdfViewerController = PdfViewerController();
  ScrollController _scrollController = ScrollController();



  /// Variables
  int _lastClosedPage = 1;
  int _currentPage = 1;
  bool _memoMode = false;
  int drawingPage = 1;
  bool isPenTouched = false;

  /// pen size control widget Entry
  bool isOverlayVisible = false;
  OverlayEntry? entry;

  /// Hide Overlay Widget
  void hideOverlay() {
    entry?.remove();
    entry = null;
    isOverlayVisible = false;
  }

  /// Count the Total Pages of the Workbook
  int _countTotalPages(){
    int _totalPages = _pdfViewerController.pageCount;
    return _totalPages;
  }

  /// get Last closed page of workbook
  void _loadLastClosedPage() async {
    final pagePrefs = await SharedPreferences.getInstance();
    _lastClosedPage = pagePrefs.getInt('${widget.workbookName}') ?? 1;
  }

  @override
  void initState() {
    super.initState();
    // Load Pdf with landScape Mode
    SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]);
    _pdfViewerController = PdfViewerController();
    String workbookName = '${widget.workbookName}';
    _loadLastClosedPage();
    _countTotalPages();
    print("======== Loaded workbook name = ${workbookName} ==========");
  }

  @override
  Widget build(BuildContext context) {

    /// Drawing Provider
    var p = context.read<DrawingProvider>();

    /// set _lastClosedPage
    void countLastClosedPage() async {
      final pagePrefs = await SharedPreferences.getInstance();
      setState(() {
        pagePrefs.setInt('${widget.workbookName}', _lastClosedPage);
        _lastClosedPage = (pagePrefs.getInt('${widget.workbookName}') ?? 1);
      });
    }

    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Colors.white,
        elevation: 1,
      ),
      body: Stack(
        children: [
          InteractiveViewer(
            panEnabled: _memoMode ? false : true,
            scaleEnabled: _memoMode ? false : true,
            maxScale: 3,
            child: Stack(
              children: [
                IgnorePointer(
                  ignoring: true,
                  child: SfPdfViewer.network(
                    widget.downloadedURL,
                    controller: _pdfViewerController,
                    key: _pdfViewerKey,
                    pageLayoutMode: PdfPageLayoutMode.single,
                    enableDoubleTapZooming: false,
                    // Save the last closed page number
                    onPageChanged: (details) {
                      _lastClosedPage = details.newPageNumber;
                      _currentPage = details.newPageNumber;
                      countLastClosedPage();
                    },
                    onDocumentLoaded: (details) {
                      _pdfViewerController.jumpToPage(_lastClosedPage);
                      _pdfViewerController.zoomLevel = 0;
                      print("totalpages = ${_countTotalPages().toInt()}");

                    },
                    canShowScrollHead: false,
                  ),
                ),
                SingleChildScrollView(
                  physics: NeverScrollableScrollPhysics(),
                  controller: _scrollController,
                  scrollDirection: Axis.horizontal,
                  child: WorkbookDrawingPage(widget.workbookName, _countTotalPages().toInt(),),
                ),
              ],
            ),
          ),

WorkbookDawingPage.dart

class WorkbookDrawingPage extends StatefulWidget {
  String workbookName;
  int countTotalPages;

  WorkbookDrawingPage(this.workbookName, this.countTotalPages,{super.key});

  @override
  State<WorkbookDrawingPage> createState() => _WorkbookDrawingPageState();
}

class _WorkbookDrawingPageState extends State<WorkbookDrawingPage> {
  // OverlayEntry widget key
  GlobalKey _overlayViewKey = GlobalKey();
  Key _viewKey = UniqueKey();

  // pen size control widget Entry
  bool isOverlayVisible = false;
  OverlayEntry? entry;


  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var p = context.read<DrawingProvider>();
    void hideOverlay() {
      entry?.remove();
      entry = null;
      isOverlayVisible = false;
    }

    return Container(
      width: MediaQuery.of(context).size.width * (widget.countTotalPages),
      height: MediaQuery.of(context).size.height * 1,
      child: CustomPaint(
        painter: DrawingPainter(p.lines),
        child: Listener(
          behavior: HitTestBehavior.translucent,
          // Draw lines when stylus hit the screen
          onPointerDown: (s) async {
            if (s.kind == PointerDeviceKind.stylus) {
              setState(() {
                p.penMode ? p.penDrawStart(s.localPosition) : null;
                p.highlighterMode
                    ? p.highlighterDrawStart(s.localPosition)
                    : null;
                p.eraseMode ? p.erase(s.localPosition) : null;
              });
            }
            /// Stylus with button pressed touched the Screen
            else if (s.kind == PointerDeviceKind.stylus ||
                s.buttons == kPrimaryStylusButton) {
              setState(() {
                p.changeEraseModeButtonClicked = true;
              });
            }
          },
          onPointerMove: (s) {
            if (s.kind == PointerDeviceKind.stylus) {
              setState(() {
                p.penMode ? p.penDrawing(s.localPosition) : null;
                p.highlighterMode
                    ? p.highlighterDrawing(s.localPosition)
                    : null;
                p.eraseMode ? p.erase(s.localPosition) : null;
              });
            } else if (s.kind == PointerDeviceKind.stylus ||
                s.buttons == kPrimaryStylusButton) {
              setState(() {
                p.changeEraseModeButtonClicked = true;
              });
            }
          },

        ),
      ),
    );
  }
}

The application uses provider for drawing lines and here's provider class

Drawing Provider

Class DrawingProvider extends ChangeNotifier {
  
 /// line List  **/
  final lines = <List<DotInfo>>[];


  Color _selectedColor = Colors.black;

  Color get selectedColor => _selectedColor;

  /** function method to change Color **/
  set changeColor(Color color) {
    _selectedColor = color;
    notifyListeners();
  }

  /** Mode Selection  **/
  bool _penMode = false;
  bool get penMode => _penMode;
  bool _highlighterMode = false;
  bool get highlighterMode => _highlighterMode;
  bool _eraseMode = false;
  bool get eraseMode => _eraseMode;
  bool _memoMode = false;
  bool get memoMode => _memoMode;


  set changeEraseModeButtonClicked(bool eraseMode) {
    eraseMode = true;
    _eraseMode = eraseMode;
    print("eraseMode가 On ");
    notifyListeners();
  }

  /** 지우개 선택 모드 **/
  void changeEraseMode() {
    _eraseMode = !_eraseMode;
    print("eraseMode is : ${eraseMode}");
    _penMode = false;
    _highlighterMode = false;
    notifyListeners();
  }

  /** 일반 펜 선택 모드 **/
  void changePenMode() {
    _penMode = !_penMode;
    // _selectedColor = _selectedColor.withOpacity(1);
    print("penMode is called : ${penMode} ");
    _eraseMode = false;
    _highlighterMode = false;
    notifyListeners();
  }

  /** 형광펜 선택 모드 **/
  void changeHighlighterMode() {
    _highlighterMode = !_highlighterMode;
    // _selectedColor = _selectedColor.withOpacity(0.3);
    print("Highlighter Mode : ${highlighterMode}");

    _eraseMode = false;
    _penMode = false;
    _opacity = 0.3;
    notifyListeners();
  }

  /** Pen Draw Start  **/
  void penDrawStart(Offset offset) async {
    var oneLine = <DotInfo>[];
    oneLine.add(DotInfo(offset, penSize, _selectedColor,));
    lines.add(oneLine);
    notifyListeners();
  }

  /** Pen Drawing  **/
  void penDrawing(Offset offset) {
    lines.last.add(DotInfo(offset, penSize, _selectedColor));
    notifyListeners();
  }

  /** highlighter Start  **/
  void highlighterDrawStart(Offset offset) {
    var oneLine = <DotInfo>[];
    oneLine
        .add(DotInfo(offset, highlighterSize, _selectedColor.withOpacity(0.3),));

    lines.add(oneLine);
    notifyListeners();
  }

  /** 터치 후 형광 펜 그리기 **/
  void highlighterDrawing(Offset offset) {
    lines.last
        .add(DotInfo(offset, highlighterSize, _selectedColor.withOpacity(0.3)));

    notifyListeners();
  }

  /** 선 지우기 메서드 **/
  void erase(Offset offset) {
    final eraseRange = 15;
    for (var oneLine in List<List<DotInfo>>.from(lines)) {
      for (var oneDot in oneLine) {
        if (sqrt(pow((offset.dx - oneDot.offset.dx), 2) +
            pow((offset.dy - oneDot.offset.dy), 2)) <
            eraseRange) {
          lines.remove(oneLine);
          break;
        }
      }
    }
    notifyListeners();
  }

}

and the Dotinfo save the information of the lines

DotInfo.dart

class DotInfo {
  final Offset offset;
  final double size;
  final Color color;

  DotInfo(this.offset, this.size, this.color);

}

in the WorkbookDrawingPage.dart , CustomPaint.painter method get DrawingPainter Class

DrawingPainter.dart

class DrawingPainter extends CustomPainter {

  final List<List<DotInfo>> lines;

  DrawingPainter(this.lines);

  @override
  void paint(Canvas canvas, Size size) {

      for (var oneLine in lines) {
        Color? color;
        double? size;
        var path = Path();
        var l = <Offset>[];
        for (var oneDot in oneLine) {
          color ??= oneDot.color;
          size ??= oneDot.size;
          l.add(oneDot.offset);
        }
        path.addPolygon(l, false);

        canvas.drawPath(
            path,
            Paint()
              ..color = color!
              ..strokeWidth = size!
              ..strokeCap = StrokeCap.round
              ..style = PaintingStyle.stroke
              ..isAntiAlias = true
              ..strokeJoin = StrokeJoin.round);
      }

  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

The Drawing function is related to the provider.

I have no idea how to save the drawn lines into the device. My idea was saving the WorkbookDrawingPage widget itself to the device by using shared preference but, shared preference only can save Int, String, double... so, I tried to encode the DotInfo Class but it cannot be encoded because constructors value is not initialized in the DotInfo Class.

how to save the drawn lines to the device and make them pair with the book?

is saving the widget to the device is possible?? or can you give me any hint or answer?

KRBILN
  • 53
  • 1
  • 6

0 Answers0