Skip to content

Commit

Permalink
Sidebar layout part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
Feichtmeier committed Apr 21, 2024
1 parent a33d01f commit c6f05ad
Show file tree
Hide file tree
Showing 15 changed files with 614 additions and 238 deletions.
7 changes: 7 additions & 0 deletions lib/build_context_x.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:flutter/material.dart';

extension BuildContextX on BuildContext {
ThemeData get theme => Theme.of(this);
bool get light => theme.brightness == Brightness.light;
MediaQueryData get mq => MediaQuery.of(this);
}
7 changes: 7 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const kAppName = 'pulse';
const kAppTitle = 'Pulse';

const kAppStateFileName = 'appstate.json';
const kSettingsFileName = 'settings.json';
const kFavLocationsFileName = 'favlocations.json';
const kLastLocation = 'lastLocation';
25 changes: 20 additions & 5 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,38 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geocoding_resolver/geocoding_resolver.dart';
import 'package:geolocator/geolocator.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/locations/locations_service.dart';
import 'src/weather/weather_model.dart';

Future<void> main() async {
await YaruWindowTitleBar.ensureInitialized();
final apiKey = await loadApiKey();
if (apiKey != null && apiKey.isNotEmpty) {
di.registerSingleton(OpenWeather(apiKey: apiKey));
di.registerSingleton(GeoCoder());
final weatherModel =
WeatherModel(openWeather: di<OpenWeather>(), geoCoder: di<GeoCoder>());
di.registerSingleton<OpenWeather>(OpenWeather(apiKey: apiKey));
di.registerSingleton<GeoCoder>(GeoCoder());
di.registerSingleton<GeolocatorPlatform>(GeolocatorPlatform.instance);
di.registerSingleton<LocationsService>(
LocationsService(),
dispose: (s) => s.dispose(),
);
final weatherModel = WeatherModel(
locationsService: di<LocationsService>(),
openWeather: di<OpenWeather>(),
geoCoder: di<GeoCoder>(),
geolocatorPlatform: di<GeolocatorPlatform>(),
);
await weatherModel.init();
di.registerSingleton(weatherModel);

di.registerSingleton(
weatherModel,
dispose: (s) => s.dispose(),
);

runApp(const App());
} else {
Expand Down
71 changes: 70 additions & 1 deletion lib/src/app/app.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';

import '../../weather.dart';
import '../weather/view/city_search_field.dart';
import '../weather/weather_model.dart';

class App extends StatelessWidget {
const App({super.key});
Expand All @@ -15,7 +18,7 @@ class App extends StatelessWidget {
debugShowCheckedModeBanner: false,
theme: yaruLight,
darkTheme: yaruDark,
home: const WeatherPage(),
home: const MasterDetailPage(),
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
Expand All @@ -28,3 +31,69 @@ class App extends StatelessWidget {
);
}
}

class MasterDetailPage extends StatelessWidget with WatchItMixin {
const MasterDetailPage({super.key});

@override
Widget build(BuildContext context) {
final model = di<WeatherModel>();
final favLocationsLength =
watchPropertyValue((WeatherModel m) => m.favLocations.length);
final favLocations = watchPropertyValue((WeatherModel m) => m.favLocations);
final lastLocation = watchPropertyValue((WeatherModel m) => m.lastLocation);
return YaruMasterDetailPage(
controller: YaruPageController(
length: favLocationsLength == 0 ? 1 : favLocationsLength,
),
tileBuilder: (context, index, selected, availableWidth) {
final location = favLocations.elementAt(index);
return YaruMasterTile(
// TODO: assign pages to location
onTap: () {},
selected: lastLocation == location,
title: Text(
favLocations.elementAt(index),
),
trailing: favLocationsLength > 1
? IconButton(
onPressed: () {
model.removeFavLocation(location).then(
(value) => model.init(
cityName: favLocations.lastOrNull,
),
);
},
icon: const Icon(
YaruIcons.window_close,
),
)
: null,
);
},
pageBuilder: (context, index) {
return const WeatherPage();
},
appBar: YaruDialogTitleBar(
backgroundColor: YaruMasterDetailTheme.of(context).sideBarColor,
border: BorderSide.none,
style: YaruTitleBarStyle.undecorated,
leading: Center(
child: YaruIconButton(
padding: EdgeInsets.zero,
icon: const Icon(
Icons.location_on,
size: 16,
),
onPressed: () => model.init(cityName: null),
),
),
titleSpacing: 0,
title: const Padding(
padding: EdgeInsets.only(right: 15),
child: CitySearchField(),
),
),
);
}
}
55 changes: 55 additions & 0 deletions lib/src/locations/locations_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'dart:async';

import '../../constants.dart';
import '../../utils.dart';

class LocationsService {
String? _lastLocation;
String? get lastLocation => _lastLocation;
void setLastLocation(String? value) {
if (value == _lastLocation) return;
writeAppState(kLastLocation, value).then((_) {
_lastLocationController.add(true);
_lastLocation = value;
});
}

final _lastLocationController = StreamController<bool>.broadcast();
Stream<bool> get lastLocationChanged => _lastLocationController.stream;

Set<String> _favLocations = {};
Set<String> get favLocations => _favLocations;
bool isFavLocation(String value) => _favLocations.contains(value);
final _favLocationsController = StreamController<bool>.broadcast();
Stream<bool> 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<void> removeFavLocation(String name) async {
if (!favLocations.contains(name)) return;
_favLocations.remove(name);
return writeStringIterable(
iterable: _favLocations,
filename: kFavLocationsFileName,
).then((_) => _favLocationsController.add(true));
}

Future<void> init() async {
_lastLocation = (await readAppState(kLastLocation)) as String?;
_favLocations = Set.from(
(await readStringIterable(filename: kFavLocationsFileName) ?? <String>{}),
);
}

Future<void> dispose() async {
await _favLocationsController.close();
await _lastLocationController.close();
}
}
18 changes: 4 additions & 14 deletions lib/src/weather/view/city_search_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,15 @@ class _CitySearchFieldState extends State<CitySearchField> {
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w500),
decoration: InputDecoration(
prefixIcon: const Icon(
decoration: const InputDecoration(
prefixIcon: Icon(
YaruIcons.search,
size: 15,
),
prefixIconConstraints:
const BoxConstraints(minWidth: 35, minHeight: 30),
contentPadding: const EdgeInsets.all(8),
prefixIconConstraints: BoxConstraints(minWidth: 35, minHeight: 30),
contentPadding: EdgeInsets.all(8),
filled: true,
hintText: 'Cityname',
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(40),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(40),
),
fillColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.05),
),
);
return textField;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/weather/view/forecast_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_weather_bg_null_safety/bg/weather_bg.dart';
import 'package:flutter_weather_bg_null_safety/flutter_weather_bg.dart';
import 'package:open_weather_client/models/weather_data.dart';
import '../utils.dart';
import '../weather_utils.dart';
import '../../../string_x.dart';
import '../weather_data_x.dart';

Expand Down
53 changes: 17 additions & 36 deletions lib/src/weather/view/today_tile.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_weather_bg_null_safety/bg/weather_bg.dart';
import 'package:flutter_weather_bg_null_safety/flutter_weather_bg.dart';
import 'package:open_weather_client/models/weather_data.dart';
import '../utils.dart';
import '../../../build_context_x.dart';
import '../weather_utils.dart';
import '../../../string_x.dart';
import '../weather_data_x.dart';

Expand All @@ -16,6 +15,7 @@ class TodayTile extends StatelessWidget {
final String? day;
final EdgeInsets padding;
final String? time;
final BorderRadiusGeometry? borderRadius;

const TodayTile({
super.key,
Expand All @@ -28,18 +28,18 @@ class TodayTile extends StatelessWidget {
this.day,
required this.padding,
this.time,
this.borderRadius,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final light = theme.brightness == Brightness.light;
final theme = context.theme;
final style = theme.textTheme.headlineSmall?.copyWith(
color: Colors.white,
fontSize: 20,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.8),
color: Colors.black.withOpacity(0.9),
offset: const Offset(0, 1),
blurRadius: 3,
),
Expand Down Expand Up @@ -110,41 +110,22 @@ class TodayTile extends StatelessWidget {
),
];

final banner = Card(
child: Stack(
children: [
Opacity(
opacity: light ? 1 : 0.4,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: WeatherBg(
weatherType: getWeatherType(data),
width: width ?? double.infinity,
height: height ?? double.infinity,
),
),
),
Center(
child: Wrap(
direction: Axis.vertical,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20,
runSpacing: 20,
runAlignment: WrapAlignment.center,
children: children,
),
),
],
),
);

return SizedBox(
width: width,
height: height,
child: Padding(
padding: padding,
child: banner,
child: Center(
child: Wrap(
direction: Axis.vertical,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20,
runSpacing: 20,
runAlignment: WrapAlignment.center,
children: children,
),
),
),
);
}
Expand Down
Loading

0 comments on commit c6f05ad

Please sign in to comment.