Skip to content

Commit f96ae23

Browse files
committed
chore: update flutter and remove custom settings
1 parent 8184194 commit f96ae23

35 files changed

+631
-495
lines changed

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
branches: [master]
66

77
env:
8-
FLUTTER_VERSION: '3.19.x'
8+
FLUTTER_VERSION: '3.27.4'
99

1010
jobs:
1111
analyze:
@@ -49,6 +49,6 @@ jobs:
4949
channel: 'stable'
5050
flutter-version: ${{env.FLUTTER_VERSION}}
5151
- run: sudo apt update
52-
- run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libmpv-dev
52+
- run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev
5353
- run: flutter pub get
5454
- run: flutter build linux -v

analysis_options.yaml

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@ linter:
1010
require_trailing_commas: true
1111
use_super_parameters: true
1212
close_sinks: true
13-
prefer_relative_imports: true
13+
prefer_relative_imports: true
14+
sort_pub_dependencies: true
15+
unnecessary_parenthesis: true
16+
unnecessary_await_in_return: true
17+
unnecessary_late: true
18+
unnecessary_breaks: true

l10n.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
arb-dir: lib/src/l10n
1+
arb-dir: lib/l10n
22
template-arb-file: app_en.arb
33
output-localization-file: app_localizations.dart
44
nullable-getter: false

lib/src/app/app.dart lib/app/app.dart

+45-12
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import 'package:flutter_weather_bg_null_safety/bg/weather_bg.dart';
66
import 'package:watch_it/watch_it.dart';
77
import 'package:yaru/yaru.dart';
88

9-
import '../../constants.dart';
10-
import '../../weather.dart';
11-
import '../build_context_x.dart';
9+
import '../constants.dart';
10+
import '../weather.dart';
11+
import '../extensions/build_context_x.dart';
1212
import '../l10n/l10n.dart';
1313
import '../weather/weather_model.dart';
1414
import 'side_bar.dart';
@@ -27,12 +27,12 @@ class App extends StatelessWidget {
2727
darkTheme: yaruDark.copyWith(
2828
tabBarTheme: TabBarTheme.of(context).copyWith(
2929
labelColor: Colors.white,
30-
unselectedLabelColor: Colors.white.withOpacity(
31-
0.8,
30+
unselectedLabelColor: Colors.white.withValues(
31+
alpha: 0.8,
3232
),
3333
),
3434
),
35-
home: const AppPage(),
35+
home: const StartPage(),
3636
scrollBehavior: const MaterialScrollBehavior().copyWith(
3737
dragDevices: {
3838
PointerDeviceKind.mouse,
@@ -46,6 +46,45 @@ class App extends StatelessWidget {
4646
}
4747
}
4848

49+
class StartPage extends StatefulWidget {
50+
const StartPage({super.key});
51+
52+
@override
53+
State<StartPage> createState() => _StartPageState();
54+
}
55+
56+
class _StartPageState extends State<StartPage> {
57+
late final Future<void> _allReady;
58+
59+
@override
60+
void initState() {
61+
super.initState();
62+
_allReady = di.allReady();
63+
}
64+
65+
@override
66+
Widget build(BuildContext context) => FutureBuilder(
67+
future: _allReady,
68+
builder: (context, snapshot) =>
69+
snapshot.hasData ? const AppPage() : const LoadingPage(),
70+
);
71+
}
72+
73+
class LoadingPage extends StatelessWidget {
74+
const LoadingPage({super.key});
75+
76+
@override
77+
Widget build(BuildContext context) => const Scaffold(
78+
appBar: YaruWindowTitleBar(
79+
border: BorderSide.none,
80+
backgroundColor: Colors.transparent,
81+
),
82+
body: Center(
83+
child: YaruCircularProgressIndicator(),
84+
),
85+
);
86+
}
87+
4988
class AppPage extends StatefulWidget with WatchItStatefulWidgetMixin {
5089
const AppPage({super.key});
5190

@@ -56,12 +95,6 @@ class AppPage extends StatefulWidget with WatchItStatefulWidgetMixin {
5695
class _AppPageState extends State<AppPage> {
5796
@override
5897
void initState() {
59-
YaruWindow.of(context).onClose(
60-
() async {
61-
await di.reset();
62-
return true;
63-
},
64-
);
6598
di<WeatherModel>().loadWeather();
6699
super.initState();
67100
}
File renamed without changes.

lib/src/app/offline_page.dart lib/app/offline_page.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:yaru/yaru.dart';
33

4-
import '../build_context_x.dart';
4+
import '../extensions/build_context_x.dart';
55
import '../l10n/l10n.dart';
66

77
class OfflinePage extends StatelessWidget {

lib/src/app/side_bar.dart lib/app/side_bar.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
22
import 'package:watch_it/watch_it.dart';
33
import 'package:yaru/yaru.dart';
44

5-
import '../../constants.dart';
6-
import '../build_context_x.dart';
5+
import '../constants.dart';
6+
import '../extensions/build_context_x.dart';
77
import '../weather/view/city_search_field.dart';
88
import '../weather/weather_model.dart';
99

@@ -66,7 +66,7 @@ class SideBar extends StatelessWidget with WatchItMixin {
6666
);
6767

6868
return Material(
69-
color: theme.colorScheme.surface.withOpacity(0.4),
69+
color: theme.colorScheme.surface.withValues(alpha: 0.4),
7070
child: SizedBox(
7171
width: kPaneWidth,
7272
child: Column(
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

lib/locations/locations_service.dart

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'dart:async';
2+
3+
import '../settings/settings_service.dart';
4+
5+
class LocationsService {
6+
LocationsService({
7+
required SettingsService settingsService,
8+
}) : _settingsService = settingsService;
9+
final SettingsService _settingsService;
10+
11+
String? _apiKey;
12+
String? get apiKey => _apiKey;
13+
void setApiKey(String? value) {
14+
_settingsService.setString(
15+
key: SettingKeys.apiKey,
16+
value: value ?? '',
17+
secure: true,
18+
);
19+
}
20+
21+
String? get lastLocation =>
22+
_settingsService.getString(key: SettingKeys.lastLocation);
23+
void setLastLocation(String? value) {
24+
if (value == null) return;
25+
_settingsService.setString(key: SettingKeys.lastLocation, value: value);
26+
}
27+
28+
Set<String> get favLocations =>
29+
_settingsService
30+
.getStrings(key: SettingKeys.favoriteLocations)
31+
?.toSet() ??
32+
{};
33+
bool isFavLocation(String value) => favLocations.contains(value);
34+
35+
void addFavLocation(String name) {
36+
if (favLocations.contains(name)) return;
37+
final favs = Set<String>.from(favLocations);
38+
favs.add(name);
39+
_settingsService.setStrings(
40+
key: SettingKeys.favoriteLocations,
41+
value: favs.toList(),
42+
);
43+
}
44+
45+
Future<void> removeFavLocation(String name) async {
46+
if (!favLocations.contains(name)) return;
47+
final favs = Set<String>.from(favLocations);
48+
favs.remove(name);
49+
_settingsService.setStrings(
50+
key: SettingKeys.favoriteLocations,
51+
value: favs.toList(),
52+
);
53+
}
54+
55+
Stream<bool> get propertiesChanged => _settingsService.propertiesChanged;
56+
57+
Future<void> init() async {
58+
_apiKey = await _settingsService.getStringSecure(key: SettingKeys.apiKey);
59+
}
60+
}

lib/main.dart

+3-21
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,13 @@
11
import 'package:flutter/material.dart';
2-
import 'package:open_weather_client/open_weather.dart';
3-
import 'package:watch_it/watch_it.dart';
42
import 'package:yaru/yaru.dart';
53

6-
import 'src/app/app.dart';
7-
import 'src/app/app_model.dart';
8-
import 'src/locations/locations_service.dart';
9-
import 'src/weather/weather_model.dart';
4+
import 'app/app.dart';
5+
import 'register_dependencies.dart';
106

117
Future<void> main() async {
128
await YaruWindowTitleBar.ensureInitialized();
139

14-
final locationsService = LocationsService();
15-
await locationsService.init();
16-
di.registerSingleton<LocationsService>(
17-
locationsService,
18-
dispose: (s) => s.dispose(),
19-
);
20-
di.registerSingleton(AppModel());
21-
22-
di.registerLazySingleton(
23-
() => WeatherModel(
24-
locationsService: locationsService,
25-
openWeather: OpenWeather(apiKey: locationsService.apiKey ?? ''),
26-
),
27-
dispose: (s) => s.dispose(),
28-
);
10+
registerDependencies();
2911

3012
runApp(const App());
3113
}

lib/register_dependencies.dart

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
2+
import 'package:open_weather_client/open_weather.dart';
3+
import 'package:shared_preferences/shared_preferences.dart';
4+
import 'package:watch_it/watch_it.dart';
5+
6+
import 'app/app_model.dart';
7+
import 'locations/locations_service.dart';
8+
import 'settings/settings_service.dart';
9+
import 'weather/weather_model.dart';
10+
11+
void registerDependencies() {
12+
di
13+
..registerSingleton<FlutterSecureStorage>(
14+
const FlutterSecureStorage(
15+
aOptions: AndroidOptions(encryptedSharedPreferences: true),
16+
),
17+
)
18+
..registerSingletonAsync<SharedPreferences>(
19+
() async => SharedPreferences.getInstance(),
20+
)
21+
..registerSingletonWithDependencies<SettingsService>(
22+
() => SettingsService(
23+
sharedPreferences: di<SharedPreferences>(),
24+
flutterSecureStorage: di<FlutterSecureStorage>(),
25+
),
26+
dependsOn: [SharedPreferences],
27+
dispose: (s) => s.dispose(),
28+
)
29+
..registerSingletonAsync<LocationsService>(
30+
() async {
31+
final locationsService = LocationsService(
32+
settingsService: di<SettingsService>(),
33+
);
34+
await locationsService.init();
35+
return locationsService;
36+
},
37+
dependsOn: [SettingsService],
38+
)
39+
..registerSingletonAsync<OpenWeather>(
40+
() async {
41+
final apiKey = (await di<SettingsService>()
42+
.getStringSecure(key: SettingKeys.apiKey)) ??
43+
'';
44+
return OpenWeather(apiKey: apiKey);
45+
},
46+
dependsOn: [SettingsService],
47+
)
48+
..registerSingletonWithDependencies(
49+
() => WeatherModel(
50+
locationsService: di<LocationsService>(),
51+
openWeather: di<OpenWeather>(),
52+
),
53+
dependsOn: [OpenWeather],
54+
dispose: (s) => s.dispose(),
55+
)
56+
..registerLazySingleton(AppModel.new);
57+
}

lib/settings/settings_model.dart

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'dart:async';
2+
3+
import 'package:safe_change_notifier/safe_change_notifier.dart';
4+
5+
import 'settings_service.dart';
6+
7+
class SettingsModel extends SafeChangeNotifier {
8+
SettingsModel({
9+
required SettingsService service,
10+
}) : _service = service;
11+
12+
final SettingsService _service;
13+
14+
StreamSubscription<bool>? _propertiesChangedSub;
15+
16+
String? getString({required String key}) => _service.getString(key: key);
17+
Future<String?> getStringSecure({required String key}) =>
18+
_service.getStringSecure(key: key);
19+
void setString({
20+
required String key,
21+
required String value,
22+
bool secure = false,
23+
}) async =>
24+
_service.setString(
25+
key: key,
26+
value: value,
27+
secure: secure,
28+
);
29+
30+
void init() => _propertiesChangedSub ??=
31+
_service.propertiesChanged.listen((_) => notifyListeners());
32+
33+
@override
34+
Future<void> dispose() async {
35+
await _propertiesChangedSub?.cancel();
36+
super.dispose();
37+
}
38+
}

lib/settings/settings_service.dart

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
4+
import 'package:shared_preferences/shared_preferences.dart';
5+
6+
class SettingsService {
7+
SettingsService({
8+
required SharedPreferences sharedPreferences,
9+
required FlutterSecureStorage flutterSecureStorage,
10+
}) : _preferences = sharedPreferences,
11+
_flutterSecureStorage = flutterSecureStorage;
12+
13+
final SharedPreferences _preferences;
14+
final FlutterSecureStorage _flutterSecureStorage;
15+
final _propertiesChangedController = StreamController<bool>.broadcast();
16+
Stream<bool> get propertiesChanged => _propertiesChangedController.stream;
17+
void notify(bool saved) {
18+
if (saved) _propertiesChangedController.add(true);
19+
}
20+
21+
String? getString({required String key}) => _preferences.getString(key);
22+
Future<String?> getStringSecure({required String key}) =>
23+
_flutterSecureStorage.read(key: key);
24+
void setString({
25+
required String key,
26+
required String value,
27+
bool secure = false,
28+
}) =>
29+
secure
30+
? _flutterSecureStorage
31+
.write(key: key, value: value)
32+
.then((_) => _propertiesChangedController.add(true))
33+
: _preferences.setString(key, value).then(notify);
34+
35+
List<String>? getStrings({required String key}) =>
36+
_preferences.getStringList(key);
37+
void setStrings({required String key, required List<String> value}) =>
38+
_preferences.setStringList(key, value).then(notify);
39+
40+
Future<void> dispose() async => _propertiesChangedController.close();
41+
}
42+
43+
class SettingKeys {
44+
static const String apiKey = 'apiKey';
45+
static const favoriteLocations = 'favoriteLocations';
46+
static const lastLocation = 'lastLocation';
47+
}

0 commit comments

Comments
 (0)