Skip to content

Commit

Permalink
add caching next lessons for ios widget
Browse files Browse the repository at this point in the history
  • Loading branch information
smart7even committed Jan 12, 2024
1 parent 2b5aacb commit 50bdce9
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 5 deletions.
6 changes: 6 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4AC4552286D0AE5C2D060267 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B630AA0B7629E0911B1F13C6 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7C85D8B02B51D8EB002E371C /* LessonRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C85D8AF2B51D8EB002E371C /* LessonRepository.swift */; };
7C85D8B12B51D8EB002E371C /* LessonRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C85D8AF2B51D8EB002E371C /* LessonRepository.swift */; };
7CBA2ACC2A9284E800F8DB4E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CBA2ACB2A9284E800F8DB4E /* WidgetKit.framework */; };
7CBA2ACE2A9284E800F8DB4E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CBA2ACD2A9284E800F8DB4E /* SwiftUI.framework */; };
7CBA2AD12A9284E800F8DB4E /* UWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBA2AD02A9284E800F8DB4E /* UWidgetBundle.swift */; };
Expand Down Expand Up @@ -67,6 +69,7 @@
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7C85D8AF2B51D8EB002E371C /* LessonRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonRepository.swift; sourceTree = "<group>"; };
7CBA2ACA2A9284E800F8DB4E /* UWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = UWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
7CBA2ACB2A9284E800F8DB4E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
7CBA2ACD2A9284E800F8DB4E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -141,6 +144,7 @@
7CBA2AD62A9284E900F8DB4E /* Info.plist */,
7CBA2AE12A929F8700F8DB4E /* Lesson.swift */,
7CBA2AE42A92A04300F8DB4E /* LessonDataProvider.swift */,
7C85D8AF2B51D8EB002E371C /* LessonRepository.swift */,
);
path = UWidget;
sourceTree = "<group>";
Expand Down Expand Up @@ -377,6 +381,7 @@
buildActionMask = 2147483647;
files = (
7CBA2AE62A92A04300F8DB4E /* LessonDataProvider.swift in Sources */,
7C85D8B12B51D8EB002E371C /* LessonRepository.swift in Sources */,
7CBA2AD12A9284E800F8DB4E /* UWidgetBundle.swift in Sources */,
7CBA2AE32A92A00700F8DB4E /* Lesson.swift in Sources */,
7CBA2AD32A9284E800F8DB4E /* UWidget.swift in Sources */,
Expand All @@ -388,6 +393,7 @@
buildActionMask = 2147483647;
files = (
7CBA2AE52A92A04300F8DB4E /* LessonDataProvider.swift in Sources */,
7C85D8B02B51D8EB002E371C /* LessonRepository.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
7CBA2AE22A929F8700F8DB4E /* Lesson.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
Expand Down
58 changes: 55 additions & 3 deletions ios/UWidget/LessonDataProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct LessonResponse: Codable {
}

protocol DataProvider {
func fetchData(groupId: Int, completion: @escaping (Lesson?) -> Void)
func fetchData(groupId: Int, completion: @escaping ([Lesson]?) -> Void)
// Add any other methods or properties that the data provider should have.
}

Expand All @@ -27,7 +27,7 @@ class ServerDataProvider: DataProvider {
self.baseURL = baseURL
}

func fetchData(groupId: Int, completion: @escaping (Lesson?) -> Void) {
func fetchData(groupId: Int, completion: @escaping ([Lesson]?) -> Void) {
// Create a URL object
guard let url = URL(string: "\(baseURL)/group/\(groupId)/lessons/next") else {
print("Invalid URL")
Expand Down Expand Up @@ -71,7 +71,7 @@ class ServerDataProvider: DataProvider {
})
let lessonResponse = try decoder.decode(LessonResponse.self, from: data)

completion(lessonResponse.lessons.first)
completion(lessonResponse.lessons)
} catch {
print("Decoding error: \(error)")
completion(nil)
Expand All @@ -88,3 +88,55 @@ class ServerDataProvider: DataProvider {

// Implement any other methods from the DataProvider protocol.
}

extension FileManager {
static func documentsDirectory() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
}


struct LessonsRecord: Codable {
let lessons: [Lesson]
let savedDate: Date
let groupId: Int
}

protocol ILocalLessonDataProvider {
func fetchData(groupId: Int) -> LessonsRecord?
func saveData(lessonsRecord: LessonsRecord)
}

class LocalLessonDataProvider: ILocalLessonDataProvider {
func fetchData(groupId: Int) -> LessonsRecord? {
let url = getUrl(groupId: groupId)

do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(LessonsRecord.self, from: data)
} catch {
print("Error loading lessons from LocalLessonDataProvider: \(error)")
return nil
}
}

func saveData(lessonsRecord: LessonsRecord) {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

do {
let data = try encoder.encode(lessonsRecord)
let url = getUrl(groupId: lessonsRecord.groupId)
try data.write(to: url)
} catch {
print("Error saving lessons to LocalLessonDataProvider: \(error)")
}
}

func getUrl(groupId: Int) -> URL {
return FileManager.documentsDirectory().appendingPathComponent("lessons_\(groupId)")
}
}

55 changes: 55 additions & 0 deletions ios/UWidget/LessonRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// LessonRepository.swift
// Runner
//
// Created by Oleg on 12.01.2024.
//

import Foundation

protocol ILessonRepository {
func fetchData(groupId: Int, completion: @escaping (Lesson?) -> Void)
}

class LessonRepository : ILessonRepository {
let remoteDataProvider: DataProvider
let localDataProvider: ILocalLessonDataProvider

init(remoteDataProvider: DataProvider, localDataProvider: ILocalLessonDataProvider) {
self.remoteDataProvider = remoteDataProvider
self.localDataProvider = localDataProvider
}

func fetchData(groupId: Int, completion: @escaping (Lesson?) -> Void) {
self.remoteDataProvider.fetchData(groupId: groupId) { lessons in
if (lessons == nil) {
let lessonsRecord = self.localDataProvider.fetchData(groupId: groupId)

if (lessonsRecord == nil) {
completion(nil)
return
}

let lessons = lessonsRecord?.lessons

if (lessons == nil || lessons?.isEmpty ?? true) {
completion(nil)
return
}

completion(lessons?.first)
return
}

if let lessons = lessons {
let lessonsRecord = LessonsRecord(
lessons: lessons, savedDate: Date(), groupId: groupId
)

self.localDataProvider.saveData(lessonsRecord: lessonsRecord)

completion(lessons.first)
}
}
}
}
8 changes: 7 additions & 1 deletion ios/UWidget/UWidget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ struct Provider: TimelineProvider {

print("groupId \(groupId)")

let dataProvider: DataProvider = ServerDataProvider(baseURL: "https://roadmapik.com:5000")
let dataProvider: ILessonRepository = LessonRepository(
remoteDataProvider: ServerDataProvider(
baseURL: "https://roadmapik.com:5000"
),
localDataProvider: LocalLessonDataProvider()
)

dataProvider.fetchData(groupId: groupId) { lesson in
let entry = SimpleEntry(date: Date(), lesson: lesson, isResponseFromServer: true)
let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 30, to: Date())!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Future<Dependencies> $initializeDependencies({
);
}
}

return dependencies;
}

Expand Down
1 change: 0 additions & 1 deletion lib/feature/schedule/widget/schedule_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:l/l.dart';
import 'package:octopus/octopus.dart';
import 'package:uneconly/common/localization/localization.dart';
import 'package:uneconly/common/model/dependencies.dart';
Expand Down
133 changes: 133 additions & 0 deletions lib/feature/settings/widget/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,20 @@ class _SettingsPageState extends State<SettingsPage> {
// ],
// ),

// Open ListSectionInsetExample widget
// CupertinoButton(
// onPressed: () {
// Navigator.of(context).push(
// CupertinoPageRoute<void>(
// builder: (BuildContext context) {
// return const ListSectionInsetExample();
// },
// ),
// );
// },
// child: const Text('Open ListSectionInsetExample'),
// ),

Text('${AppLocalizations.of(context)!.theme}: '),
const SizedBox(height: 10),
// horizontal list of themes
Expand Down Expand Up @@ -238,3 +252,122 @@ class _SettingsPageState extends State<SettingsPage> {
);
}
}

class CupertinoListSectionInsetApp extends StatelessWidget {
const CupertinoListSectionInsetApp({super.key});

@override
Widget build(BuildContext context) {
return const CupertinoApp(
home: ListSectionInsetExample(),
);
}
}

class ListSectionInsetExample extends StatefulWidget {
const ListSectionInsetExample({super.key});

@override
State<ListSectionInsetExample> createState() =>
_ListSectionInsetExampleState();
}

class _ListSectionInsetExampleState extends State<ListSectionInsetExample> {
bool _isNotificationsEnabled = true;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ListSectionInsetExample'),
),
body: CupertinoListSection.insetGrouped(
header: const Text('My Settings'),
children: <CupertinoListTile>[
CupertinoListTile.notched(
title: const Text('Open pull request'),
leading: Container(
width: double.infinity,
height: double.infinity,
color: CupertinoColors.activeGreen,
),
trailing: const CupertinoListTileChevron(),
onTap: () => Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const _SecondPage(text: 'Open pull request');
},
),
),
),
CupertinoListTile.notched(
title: const Text('Push to master'),
leading: Container(
width: double.infinity,
height: double.infinity,
color: CupertinoColors.systemRed,
),
additionalInfo: const Text('Not available'),
),
CupertinoListTile.notched(
title: const Text('View last commit'),
leading: Container(
width: double.infinity,
height: double.infinity,
color: CupertinoColors.activeOrange,
),
additionalInfo: const Text('12 days ago'),
trailing: const CupertinoListTileChevron(),
onTap: () => Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const _SecondPage(text: 'Last commit');
},
),
),
),
CupertinoListTile.notched(
title: const Text('Notifications'),
leading: Container(
width: double.infinity,
height: double.infinity,
color: CupertinoColors.activeBlue,
),
trailing: CupertinoSwitch(
value: _isNotificationsEnabled,
onChanged: (value) {
setState(
() {
_isNotificationsEnabled = value;
},
);
},
),
onTap: () => Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const _SecondPage(text: 'Last commit');
},
),
),
),
],
),
);
}
}

class _SecondPage extends StatelessWidget {
const _SecondPage({required this.text});

final String text;

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(text),
),
);
}
}

0 comments on commit 50bdce9

Please sign in to comment.