diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6b95854..34c70d5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,7 +5,7 @@ on:
branches: [master]
env:
- FLUTTER_VERSION: '3.19.x'
+ FLUTTER_VERSION: '3.27.4'
jobs:
analyze:
@@ -49,6 +49,6 @@ jobs:
channel: 'stable'
flutter-version: ${{env.FLUTTER_VERSION}}
- run: sudo apt update
- - 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
+ - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libsecret-1-dev
- run: flutter pub get
- run: flutter build linux -v
\ No newline at end of file
diff --git a/.github/workflows/snap.yaml b/.github/workflows/snap.yaml
new file mode 100644
index 0000000..d1fd349
--- /dev/null
+++ b/.github/workflows/snap.yaml
@@ -0,0 +1,59 @@
+name: Create Snap Package
+
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+env:
+ FLUTTER_VERSION: "3.27.4"
+
+jobs:
+ build_and_release_linux_snap_edge_amd64:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 5
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: "stable"
+ flutter-version: ${{env.FLUTTER_VERSION}}
+ - run: sudo apt update
+ - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libsecret-1-dev
+ - run: flutter pub get
+ - run: flutter build linux --release --dart-define=${{ secrets.API_KEY }}
+ - uses: snapcore/action-build@v1
+ id: build
+ - uses: snapcore/action-publish@v1
+ if: steps.build.outcome == 'success'
+ env:
+ SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
+ with:
+ snap: ${{ steps.build.outputs.snap }}
+ release: edge
+
+ build_and_release_linux_snap_edge_arm64:
+ runs-on: ubuntu-24.04-arm
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 5
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: "stable"
+ flutter-version: ${{env.FLUTTER_VERSION}}
+ - run: sudo apt update
+ - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libsecret-1-dev
+ - run: flutter pub get
+ - run: flutter build linux --release --dart-define=${{ secrets.API_KEY }}
+ - uses: snapcore/action-build@v1
+ id: build
+ - uses: snapcore/action-publish@v1
+ if: steps.build.outcome == 'success'
+ env:
+ SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
+ with:
+ snap: ${{ steps.build.outputs.snap }}
+ release: edge
diff --git a/README.md b/README.md
index 89cf0a7..f350b75 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,13 @@
Requires an api key from [openweathermap](https://openweathermap.org) which you need to create yourself (free tier) in your own account.
+
## Build
+### Install libsecret
+
+`sudo apt install libsecret-1-dev`
+
### Install Flutter
@@ -22,3 +27,13 @@ sudo apt -y install git curl cmake meson make clang libgtk-3-dev pkg-config && m
```
+
+### Run the app
+
+For the first run or after every `flutter clean`, the app needs to be started with your API_KEY:
+
+`flutter run --dart-define=API_KEY=YOUR_API_KEY_HERE_WITHOUT_QUOTES`
+
+any other times you can use
+
+`flutter run`
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 90633ee..1ee12d1 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -10,4 +10,9 @@ linter:
require_trailing_commas: true
use_super_parameters: true
close_sinks: true
- prefer_relative_imports: true
\ No newline at end of file
+ prefer_relative_imports: true
+ sort_pub_dependencies: true
+ unnecessary_parenthesis: true
+ unnecessary_await_in_return: true
+ unnecessary_late: true
+ unnecessary_breaks: true
\ No newline at end of file
diff --git a/l10n.yaml b/l10n.yaml
index 03f4a93..c3dc2f3 100644
--- a/l10n.yaml
+++ b/l10n.yaml
@@ -1,4 +1,4 @@
-arb-dir: lib/src/l10n
+arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
nullable-getter: false
diff --git a/lib/app/app.dart b/lib/app/app.dart
new file mode 100644
index 0000000..4de2044
--- /dev/null
+++ b/lib/app/app.dart
@@ -0,0 +1,133 @@
+import 'dart:io';
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_weather_bg_null_safety/bg/weather_bg.dart';
+import 'package:watch_it/watch_it.dart';
+import 'package:yaru/yaru.dart';
+
+import '../constants.dart';
+import '../weather.dart';
+import '../extensions/build_context_x.dart';
+import '../l10n/l10n.dart';
+import '../weather/weather_model.dart';
+import 'side_bar.dart';
+
+class App extends StatelessWidget {
+ const App({super.key});
+
+ @override
+ Widget build(BuildContext context) => MaterialApp(
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: supportedLocales,
+ onGenerateTitle: (context) => 'MusicPod',
+ debugShowCheckedModeBanner: false,
+ theme: yaruLight,
+ darkTheme: yaruDark.copyWith(
+ tabBarTheme: TabBarTheme.of(context).copyWith(
+ labelColor: Colors.white,
+ unselectedLabelColor: Colors.white.withValues(
+ alpha: 0.8,
+ ),
+ ),
+ ),
+ home: const _StartPage(),
+ scrollBehavior: const MaterialScrollBehavior().copyWith(
+ dragDevices: {
+ PointerDeviceKind.mouse,
+ PointerDeviceKind.touch,
+ PointerDeviceKind.stylus,
+ PointerDeviceKind.unknown,
+ PointerDeviceKind.trackpad,
+ },
+ ),
+ );
+}
+
+class _StartPage extends StatefulWidget {
+ const _StartPage();
+
+ @override
+ State<_StartPage> createState() => _StartPageState();
+}
+
+class _StartPageState extends State<_StartPage> {
+ late final Future _allReady;
+
+ @override
+ void initState() {
+ super.initState();
+ _allReady = di.allReady();
+ }
+
+ @override
+ Widget build(BuildContext context) => FutureBuilder(
+ future: _allReady,
+ builder: (context, snapshot) =>
+ snapshot.hasData ? const _AppPage() : const _LoadingPage(),
+ );
+}
+
+class _LoadingPage extends StatelessWidget {
+ const _LoadingPage();
+
+ @override
+ Widget build(BuildContext context) => const Scaffold(
+ appBar: YaruWindowTitleBar(
+ border: BorderSide.none,
+ backgroundColor: Colors.transparent,
+ ),
+ body: Center(
+ child: YaruCircularProgressIndicator(),
+ ),
+ );
+}
+
+class _AppPage extends StatefulWidget with WatchItStatefulWidgetMixin {
+ const _AppPage();
+
+ @override
+ State<_AppPage> createState() => _AppPageState();
+}
+
+class _AppPageState extends State<_AppPage> {
+ @override
+ void initState() {
+ di().loadWeather();
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final weatherType = watchPropertyValue((WeatherModel m) => m.weatherType);
+
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ var list = [
+ if (constraints.maxWidth > kBreakPoint) const SideBar(),
+ Expanded(
+ child: WeatherPage(
+ showDrawer: constraints.maxWidth < kBreakPoint,
+ ),
+ ),
+ ];
+ return Stack(
+ alignment: Alignment.center,
+ children: [
+ Opacity(
+ opacity: Platform.isMacOS ? (context.light ? 1 : 0.6) : 0.7,
+ child: WeatherBg(
+ weatherType: weatherType,
+ width: constraints.maxWidth,
+ height: constraints.maxHeight,
+ ),
+ ),
+ Row(
+ children: Platform.isMacOS ? list.reversed.toList() : list,
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/src/app/app_model.dart b/lib/app/app_model.dart
similarity index 100%
rename from lib/src/app/app_model.dart
rename to lib/app/app_model.dart
diff --git a/lib/src/app/offline_page.dart b/lib/app/offline_page.dart
similarity index 97%
rename from lib/src/app/offline_page.dart
rename to lib/app/offline_page.dart
index 8707eea..c432c77 100644
--- a/lib/src/app/offline_page.dart
+++ b/lib/app/offline_page.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:yaru/yaru.dart';
-import '../build_context_x.dart';
+import '../extensions/build_context_x.dart';
import '../l10n/l10n.dart';
class OfflinePage extends StatelessWidget {
diff --git a/lib/src/app/side_bar.dart b/lib/app/side_bar.dart
similarity index 95%
rename from lib/src/app/side_bar.dart
rename to lib/app/side_bar.dart
index 59aa2c1..497ce27 100644
--- a/lib/src/app/side_bar.dart
+++ b/lib/app/side_bar.dart
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';
-import '../../constants.dart';
-import '../build_context_x.dart';
+import '../constants.dart';
+import '../extensions/build_context_x.dart';
import '../weather/view/city_search_field.dart';
import '../weather/weather_model.dart';
@@ -66,7 +66,7 @@ class SideBar extends StatelessWidget with WatchItMixin {
);
return Material(
- color: theme.colorScheme.surface.withOpacity(0.4),
+ color: theme.colorScheme.surface.withValues(alpha: 0.4),
child: SizedBox(
width: kPaneWidth,
child: Column(
diff --git a/lib/src/build_context_x.dart b/lib/extensions/build_context_x.dart
similarity index 100%
rename from lib/src/build_context_x.dart
rename to lib/extensions/build_context_x.dart
diff --git a/lib/string_x.dart b/lib/extensions/string_x.dart
similarity index 100%
rename from lib/string_x.dart
rename to lib/extensions/string_x.dart
diff --git a/lib/src/l10n/app_de.arb b/lib/l10n/app_de.arb
similarity index 100%
rename from lib/src/l10n/app_de.arb
rename to lib/l10n/app_de.arb
diff --git a/lib/src/l10n/app_en.arb b/lib/l10n/app_en.arb
similarity index 100%
rename from lib/src/l10n/app_en.arb
rename to lib/l10n/app_en.arb
diff --git a/lib/src/l10n/l10n.dart b/lib/l10n/l10n.dart
similarity index 100%
rename from lib/src/l10n/l10n.dart
rename to lib/l10n/l10n.dart
diff --git a/lib/locations/locations_service.dart b/lib/locations/locations_service.dart
new file mode 100644
index 0000000..9916dc2
--- /dev/null
+++ b/lib/locations/locations_service.dart
@@ -0,0 +1,46 @@
+import 'dart:async';
+
+import '../settings/settings_service.dart';
+
+class LocationsService {
+ LocationsService({
+ required SettingsService settingsService,
+ }) : _settingsService = settingsService;
+ final SettingsService _settingsService;
+
+ String? get lastLocation =>
+ _settingsService.getString(key: SettingKeys.lastLocation);
+ void setLastLocation(String? value) {
+ if (value == null) return;
+ _settingsService.setString(key: SettingKeys.lastLocation, value: value);
+ }
+
+ Set get favLocations =>
+ _settingsService
+ .getStrings(key: SettingKeys.favoriteLocations)
+ ?.toSet() ??
+ {};
+ bool isFavLocation(String value) => favLocations.contains(value);
+
+ void addFavLocation(String name) {
+ if (favLocations.contains(name)) return;
+ final favs = Set.from(favLocations);
+ favs.add(name);
+ _settingsService.setStrings(
+ key: SettingKeys.favoriteLocations,
+ value: favs.toList(),
+ );
+ }
+
+ Future removeFavLocation(String name) async {
+ if (!favLocations.contains(name)) return;
+ final favs = Set.from(favLocations);
+ favs.remove(name);
+ _settingsService.setStrings(
+ key: SettingKeys.favoriteLocations,
+ value: favs.toList(),
+ );
+ }
+
+ Stream get propertiesChanged => _settingsService.propertiesChanged;
+}
diff --git a/lib/main.dart b/lib/main.dart
index ffe9698..0189cc0 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,31 +1,13 @@
import 'package:flutter/material.dart';
-import 'package:open_weather_client/open_weather.dart';
-import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';
-import 'src/app/app.dart';
-import 'src/app/app_model.dart';
-import 'src/locations/locations_service.dart';
-import 'src/weather/weather_model.dart';
+import 'app/app.dart';
+import 'register_dependencies.dart';
Future main() async {
await YaruWindowTitleBar.ensureInitialized();
- final locationsService = LocationsService();
- await locationsService.init();
- di.registerSingleton(
- locationsService,
- dispose: (s) => s.dispose(),
- );
- di.registerSingleton(AppModel());
-
- di.registerLazySingleton(
- () => WeatherModel(
- locationsService: locationsService,
- openWeather: OpenWeather(apiKey: locationsService.apiKey ?? ''),
- ),
- dispose: (s) => s.dispose(),
- );
+ registerDependencies();
runApp(const App());
}
diff --git a/lib/register_dependencies.dart b/lib/register_dependencies.dart
new file mode 100644
index 0000000..f09dc50
--- /dev/null
+++ b/lib/register_dependencies.dart
@@ -0,0 +1,61 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:open_weather_client/open_weather.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:watch_it/watch_it.dart';
+
+import 'app/app_model.dart';
+import 'locations/locations_service.dart';
+import 'settings/settings_service.dart';
+import 'weather/weather_model.dart';
+
+void registerDependencies() {
+ di
+ ..registerSingletonAsync(
+ () async {
+ const flutterSecureStorage = FlutterSecureStorage(
+ aOptions: AndroidOptions(encryptedSharedPreferences: true),
+ );
+
+ const apiKey = String.fromEnvironment('API_KEY', defaultValue: '');
+
+ if (apiKey.isNotEmpty) {
+ flutterSecureStorage.write(key: SettingKeys.apiKey, value: apiKey);
+ }
+ return flutterSecureStorage;
+ },
+ )
+ ..registerSingletonAsync(
+ () async => SharedPreferences.getInstance(),
+ )
+ ..registerSingletonWithDependencies(
+ () => SettingsService(
+ sharedPreferences: di(),
+ flutterSecureStorage: di(),
+ ),
+ dependsOn: [FlutterSecureStorage, SharedPreferences],
+ dispose: (s) => s.dispose(),
+ )
+ ..registerSingletonWithDependencies(
+ () => LocationsService(
+ settingsService: di(),
+ ),
+ dependsOn: [SettingsService],
+ )
+ ..registerSingletonAsync(
+ () async => OpenWeather(
+ apiKey:
+ await di().read(key: SettingKeys.apiKey) ??
+ '',
+ ),
+ dependsOn: [SettingsService],
+ )
+ ..registerSingletonWithDependencies(
+ () => WeatherModel(
+ locationsService: di(),
+ openWeather: di(),
+ ),
+ dependsOn: [OpenWeather],
+ dispose: (s) => s.dispose(),
+ )
+ ..registerLazySingleton(AppModel.new);
+}
diff --git a/lib/settings/settings_model.dart b/lib/settings/settings_model.dart
new file mode 100644
index 0000000..1b39efa
--- /dev/null
+++ b/lib/settings/settings_model.dart
@@ -0,0 +1,38 @@
+import 'dart:async';
+
+import 'package:safe_change_notifier/safe_change_notifier.dart';
+
+import 'settings_service.dart';
+
+class SettingsModel extends SafeChangeNotifier {
+ SettingsModel({
+ required SettingsService service,
+ }) : _service = service;
+
+ final SettingsService _service;
+
+ StreamSubscription? _propertiesChangedSub;
+
+ String? getString({required String key}) => _service.getString(key: key);
+ Future getStringSecure({required String key}) =>
+ _service.getStringSecure(key: key);
+ void setString({
+ required String key,
+ required String value,
+ bool secure = false,
+ }) async =>
+ _service.setString(
+ key: key,
+ value: value,
+ secure: secure,
+ );
+
+ void init() => _propertiesChangedSub ??=
+ _service.propertiesChanged.listen((_) => notifyListeners());
+
+ @override
+ Future dispose() async {
+ await _propertiesChangedSub?.cancel();
+ super.dispose();
+ }
+}
diff --git a/lib/settings/settings_service.dart b/lib/settings/settings_service.dart
new file mode 100644
index 0000000..c0ffce2
--- /dev/null
+++ b/lib/settings/settings_service.dart
@@ -0,0 +1,47 @@
+import 'dart:async';
+
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class SettingsService {
+ SettingsService({
+ required SharedPreferences sharedPreferences,
+ required FlutterSecureStorage flutterSecureStorage,
+ }) : _preferences = sharedPreferences,
+ _flutterSecureStorage = flutterSecureStorage;
+
+ final SharedPreferences _preferences;
+ final FlutterSecureStorage _flutterSecureStorage;
+ final _propertiesChangedController = StreamController.broadcast();
+ Stream get propertiesChanged => _propertiesChangedController.stream;
+ void notify(bool saved) {
+ if (saved) _propertiesChangedController.add(true);
+ }
+
+ String? getString({required String key}) => _preferences.getString(key);
+ Future getStringSecure({required String key}) =>
+ _flutterSecureStorage.read(key: key);
+ void setString({
+ required String key,
+ required String value,
+ bool secure = false,
+ }) =>
+ secure
+ ? _flutterSecureStorage
+ .write(key: key, value: value)
+ .then((_) => _propertiesChangedController.add(true))
+ : _preferences.setString(key, value).then(notify);
+
+ List? getStrings({required String key}) =>
+ _preferences.getStringList(key);
+ void setStrings({required String key, required List value}) =>
+ _preferences.setStringList(key, value).then(notify);
+
+ Future dispose() async => _propertiesChangedController.close();
+}
+
+class SettingKeys {
+ static const String apiKey = 'apiKey';
+ static const favoriteLocations = 'favoriteLocations';
+ static const lastLocation = 'lastLocation';
+}
diff --git a/lib/src/app/app.dart b/lib/src/app/app.dart
deleted file mode 100644
index a1f4e5d..0000000
--- a/lib/src/app/app.dart
+++ /dev/null
@@ -1,102 +0,0 @@
-import 'dart:io';
-import 'dart:ui';
-
-import 'package:flutter/material.dart';
-import 'package:flutter_weather_bg_null_safety/bg/weather_bg.dart';
-import 'package:watch_it/watch_it.dart';
-import 'package:yaru/yaru.dart';
-
-import '../../constants.dart';
-import '../../weather.dart';
-import '../build_context_x.dart';
-import '../l10n/l10n.dart';
-import '../weather/weather_model.dart';
-import 'side_bar.dart';
-
-class App extends StatelessWidget {
- const App({super.key});
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- localizationsDelegates: AppLocalizations.localizationsDelegates,
- supportedLocales: supportedLocales,
- onGenerateTitle: (context) => 'MusicPod',
- debugShowCheckedModeBanner: false,
- theme: yaruLight,
- darkTheme: yaruDark.copyWith(
- tabBarTheme: TabBarTheme.of(context).copyWith(
- labelColor: Colors.white,
- unselectedLabelColor: Colors.white.withOpacity(
- 0.8,
- ),
- ),
- ),
- home: const AppPage(),
- scrollBehavior: const MaterialScrollBehavior().copyWith(
- dragDevices: {
- PointerDeviceKind.mouse,
- PointerDeviceKind.touch,
- PointerDeviceKind.stylus,
- PointerDeviceKind.unknown,
- PointerDeviceKind.trackpad,
- },
- ),
- );
- }
-}
-
-class AppPage extends StatefulWidget with WatchItStatefulWidgetMixin {
- const AppPage({super.key});
-
- @override
- State createState() => _AppPageState();
-}
-
-class _AppPageState extends State {
- @override
- void initState() {
- YaruWindow.of(context).onClose(
- () async {
- await di.reset();
- return true;
- },
- );
- di().loadWeather();
- super.initState();
- }
-
- @override
- Widget build(BuildContext context) {
- final weatherType = watchPropertyValue((WeatherModel m) => m.weatherType);
-
- return LayoutBuilder(
- builder: (context, constraints) {
- var list = [
- if (constraints.maxWidth > kBreakPoint) const SideBar(),
- Expanded(
- child: WeatherPage(
- showDrawer: constraints.maxWidth < kBreakPoint,
- ),
- ),
- ];
- return Stack(
- alignment: Alignment.center,
- children: [
- Opacity(
- opacity: Platform.isMacOS ? (context.light ? 1 : 0.6) : 0.7,
- child: WeatherBg(
- weatherType: weatherType,
- width: constraints.maxWidth,
- height: constraints.maxHeight,
- ),
- ),
- Row(
- children: Platform.isMacOS ? list.reversed.toList() : list,
- ),
- ],
- );
- },
- );
- }
-}
diff --git a/lib/src/locations/locations_service.dart b/lib/src/locations/locations_service.dart
deleted file mode 100644
index e7bbe29..0000000
--- a/lib/src/locations/locations_service.dart
+++ /dev/null
@@ -1,69 +0,0 @@
-import 'dart:async';
-
-import '../../constants.dart';
-import '../../utils.dart';
-
-class LocationsService {
- String? _apiKey;
- String? get apiKey => _apiKey;
- Future setApiKey(String? value) {
- if (value == _apiKey) return Future.value();
- return writeSetting(kApiKey, value).then((_) {
- _apiKey = value;
- _apiKeyController.add(true);
- });
- }
-
- final _apiKeyController = StreamController.broadcast();
- Stream get apiKeyChanged => _apiKeyController.stream;
-
- String? _lastLocation;
- String? get lastLocation => _lastLocation;
- void setLastLocation(String? value) {
- if (value == _lastLocation) return;
- writeAppState(kLastLocation, value).then((_) {
- _lastLocation = value;
- _lastLocationController.add(true);
- });
- }
-
- final _lastLocationController = StreamController.broadcast();
- Stream get lastLocationChanged => _lastLocationController.stream;
-
- Set _favLocations = {};
- Set get favLocations => _favLocations;
- bool isFavLocation(String value) => _favLocations.contains(value);
- final _favLocationsController = StreamController.broadcast();
- Stream get favLocationsChanged => _favLocationsController.stream;
-
- void addFavLocation(String name) {
- if (favLocations.contains(name)) return;
- _favLocations.add(name);
- writeStringIterable(
- iterable: _favLocations,
- filename: kFavLocationsFileName,
- ).then((_) => _favLocationsController.add(true));
- }
-
- Future removeFavLocation(String name) async {
- if (!favLocations.contains(name)) return;
- _favLocations.remove(name);
- return writeStringIterable(
- iterable: _favLocations,
- filename: kFavLocationsFileName,
- ).then((_) => _favLocationsController.add(true));
- }
-
- Future init() async {
- _apiKey = (await readSetting(kApiKey)) as String?;
- _lastLocation = (await readAppState(kLastLocation)) as String?;
- _favLocations = Set.from(
- (await readStringIterable(filename: kFavLocationsFileName) ?? {}),
- );
- }
-
- Future dispose() async {
- await _favLocationsController.close();
- await _lastLocationController.close();
- }
-}
diff --git a/lib/utils.dart b/lib/utils.dart
deleted file mode 100644
index 8fb5722..0000000
--- a/lib/utils.dart
+++ /dev/null
@@ -1,233 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:path/path.dart' as p;
-import 'package:path_provider/path_provider.dart';
-import 'package:xdg_directories/xdg_directories.dart';
-
-import 'constants.dart';
-
-String formatTime(Duration duration) {
- String twoDigits(int n) => n.toString().padLeft(2, '0');
- final hours = twoDigits(duration.inHours);
- final minutes = twoDigits(duration.inMinutes.remainder(60));
- final seconds = twoDigits(duration.inSeconds.remainder(60));
-
- return [if (duration.inHours > 0) hours, minutes, seconds].join(':');
-}
-
-String? _workingDir;
-Future getWorkingDir() async {
- if (_workingDir != null) return Future.value(_workingDir!);
- if (Platform.isLinux) {
- final workingDir = p.join(configHome.path, kAppName);
- if (!Directory(workingDir).existsSync()) {
- await Directory(workingDir).create();
- }
- _workingDir = workingDir;
- return workingDir;
- } else if (Platform.isMacOS || Platform.isIOS) {
- final libDirPath = (await getLibraryDirectory()).path;
- final workingDirPath = p.join(libDirPath, kAppName);
- if (!Directory(workingDirPath).existsSync()) {
- await Directory(workingDirPath).create();
- }
- _workingDir = workingDirPath;
- return workingDirPath;
- } else {
- final docDirPath = (await getApplicationSupportDirectory()).path;
- final workingDirPath = p.join(docDirPath, kAppName);
- if (!Directory(workingDirPath).existsSync()) {
- Directory(workingDirPath).createSync();
- }
- _workingDir = workingDirPath;
- return workingDirPath;
- }
-}
-
-Future getMusicDir() async {
- if (Platform.isLinux) {
- return getUserDirectory('MUSIC')?.path;
- }
- return null;
-}
-
-Future getDownloadsDir() async {
- String? path;
- if (Platform.isLinux) {
- path = getUserDirectory('DOWNLOAD')?.path;
- } else if (Platform.isMacOS || Platform.isIOS || Platform.isWindows) {
- path = (await getDownloadsDirectory())?.path;
- }
- if (path != null) {
- return p.join(path, 'musicpod');
- }
- return null;
-}
-
-Future writeAppState(String key, dynamic value) async =>
- await writeSetting(key, value, kAppStateFileName);
-
-Future writeSetting(
- String? key,
- dynamic value, [
- String filename = kSettingsFileName,
-]) async {
- if (key == null || value == null) return;
- final oldSettings = await getSettings(filename);
- if (oldSettings.containsKey(key)) {
- oldSettings.update(key, (v) => value);
- } else {
- oldSettings.putIfAbsent(key, () => value);
- }
- final jsonStr = jsonEncode(oldSettings);
-
- final workingDir = await getWorkingDir();
-
- final file = File(p.join(workingDir, filename));
-
- if (!file.existsSync()) {
- file.create();
- }
-
- await file.writeAsString(jsonStr);
-}
-
-Future readAppState(String key) => readSetting(key, kAppStateFileName);
-
-Future readSetting(
- dynamic key, [
- String filename = kSettingsFileName,
-]) async {
- if (key == null) return null;
- final oldSettings = await getSettings(filename);
- return oldSettings[key];
-}
-
-Future