Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches: [master]

env:
FLUTTER_VERSION: '3.19.x'
FLUTTER_VERSION: '3.27.4'

jobs:
analyze:
Expand Down Expand Up @@ -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
59 changes: 59 additions & 0 deletions .github/workflows/snap.yaml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<details>
Expand All @@ -22,3 +27,13 @@ sudo apt -y install git curl cmake meson make clang libgtk-3-dev pkg-config && m
```

</details>

### 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`
7 changes: 6 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ linter:
require_trailing_commas: true
use_super_parameters: true
close_sinks: true
prefer_relative_imports: true
prefer_relative_imports: true
sort_pub_dependencies: true
unnecessary_parenthesis: true
unnecessary_await_in_return: true
unnecessary_late: true
unnecessary_breaks: true
2 changes: 1 addition & 1 deletion l10n.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
133 changes: 133 additions & 0 deletions lib/app/app.dart
Original file line number Diff line number Diff line change
@@ -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<void> _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<WeatherModel>().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,
),
],
);
},
);
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions lib/src/app/side_bar.dart → lib/app/side_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
46 changes: 46 additions & 0 deletions lib/locations/locations_service.dart
Original file line number Diff line number Diff line change
@@ -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<String> 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<String>.from(favLocations);
favs.add(name);
_settingsService.setStrings(
key: SettingKeys.favoriteLocations,
value: favs.toList(),
);
}

Future<void> removeFavLocation(String name) async {
if (!favLocations.contains(name)) return;
final favs = Set<String>.from(favLocations);
favs.remove(name);
_settingsService.setStrings(
key: SettingKeys.favoriteLocations,
value: favs.toList(),
);
}

Stream<bool> get propertiesChanged => _settingsService.propertiesChanged;
}
24 changes: 3 additions & 21 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -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<void> main() async {
await YaruWindowTitleBar.ensureInitialized();

final locationsService = LocationsService();
await locationsService.init();
di.registerSingleton<LocationsService>(
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());
}
Loading