Skip to content

Commit 59cb792

Browse files
authored
Merge pull request #32 from soup-bowl/soup-bowl/android-theme
Settings option for Android theme
2 parents d11ebde + cfad19b commit 59cb792

File tree

7 files changed

+107
-39
lines changed

7 files changed

+107
-39
lines changed

frontend/src/App.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { IonReactRouter } from "@ionic/react-router"
1717
import { discOutline, searchOutline, settingsOutline, cogOutline } from "ionicons/icons"
1818
import { CollectionPage, SearchPage, SettingsPage } from "@/pages"
1919
import { createIDBPersister } from "@/persister"
20+
import { DeviceMode } from "@/types"
2021

2122
/* Core CSS required for Ionic components to work properly */
2223
import "@ionic/react/css/core.css"
@@ -48,8 +49,16 @@ import "@ionic/react/css/palettes/dark.always.css"
4849
/* Theme variables */
4950
import "./theme/variables.css"
5051

52+
const getDeviceMode = (): DeviceMode => {
53+
const item = localStorage.getItem("DeviceTheme")
54+
const parsedItem = item ? JSON.parse(item) : "ios"
55+
const validItem = parsedItem ? (parsedItem as DeviceMode) : "ios"
56+
localStorage.setItem("mode", validItem)
57+
return validItem
58+
}
59+
5160
setupIonicReact({
52-
mode: import.meta.env.VITE_APP_MODE ?? "ios",
61+
mode: getDeviceMode(),
5362
})
5463

5564
const queryClient = new QueryClient({

frontend/src/pages/Collection.tsx

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import {
1818
} from "@ionic/react"
1919
import { OverlayEventDetail } from "@ionic/react/dist/types/components/react-component-lib/interfaces"
2020
import { useQuery, useQueryClient } from "@tanstack/react-query"
21-
import { filterOutline, personOutline, pricetagOutline, timeOutline, listOutline, gridOutline } from "ionicons/icons"
21+
import { personOutline, personSharp } from "ionicons/icons"
2222
import { IReleaseSet, IReleases, getCollectionAndWants } from "@/api"
2323
import { FullpageLoading, AlbumGrid, FullpageInfo, AlbumListGroups, InfoBanners } from "@/components"
2424
import { ProfileModal, ViewAlbumDetails } from "@/modal"
2525
import { useAuth, useSettings } from "@/hooks"
26-
import { masterSort } from "@/utils"
26+
import { getFilterIcon, getLayoutIcon, masterSort } from "@/utils"
2727
import { IReleaseTuple } from "@/types"
2828

2929
const filterActionButtons = [
@@ -77,30 +77,6 @@ const CollectionPage: React.FC = () => {
7777

7878
const [{ username, token }] = useAuth()
7979

80-
const getFilterIcon = (filter: string) => {
81-
switch (filter) {
82-
default:
83-
case "none":
84-
return filterOutline
85-
case "label":
86-
return pricetagOutline
87-
case "artist":
88-
return personOutline
89-
case "release":
90-
return timeOutline
91-
}
92-
}
93-
94-
const getLayoutIcon = (item: string) => {
95-
switch (item) {
96-
default:
97-
case "grid":
98-
return gridOutline
99-
case "list":
100-
return listOutline
101-
}
102-
}
103-
10480
if (!username) {
10581
return (
10682
<IonPage>
@@ -156,7 +132,7 @@ const CollectionPage: React.FC = () => {
156132
<IonToolbar>
157133
<IonButtons slot="secondary">
158134
<IonButton onClick={() => setProfileModal(true)}>
159-
<IonIcon slot="icon-only" icon={personOutline} />
135+
<IonIcon slot="icon-only" ios={personOutline} md={personSharp} />
160136
</IonButton>
161137
</IonButtons>
162138
<IonButtons slot="primary">
@@ -169,7 +145,11 @@ const CollectionPage: React.FC = () => {
169145
}
170146
}}
171147
>
172-
<IonIcon slot="icon-only" md={getLayoutIcon(layout)} />
148+
<IonIcon
149+
slot="icon-only"
150+
ios={getLayoutIcon(layout, "ios")}
151+
md={getLayoutIcon(layout, "md")}
152+
/>
173153
</IonButton>
174154
<IonButton
175155
onClick={() =>
@@ -184,7 +164,11 @@ const CollectionPage: React.FC = () => {
184164
})
185165
}
186166
>
187-
<IonIcon slot="icon-only" md={getFilterIcon(filter)} />
167+
<IonIcon
168+
slot="icon-only"
169+
ios={getFilterIcon(filter, "ios")}
170+
md={getFilterIcon(filter, "md")}
171+
/>
188172
</IonButton>
189173
</IonButtons>
190174
<IonSegment

frontend/src/pages/Search.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
IonTitle,
1111
IonToolbar,
1212
} from "@ionic/react"
13-
import { barcodeOutline } from "ionicons/icons"
13+
import { barcodeOutline, barcodeSharp } from "ionicons/icons"
1414
import { useQuery } from "@tanstack/react-query"
1515
import { IReleaseSet, IReleases, getCollectionAndWants } from "@/api"
1616
import { AlbumList, FullpageInfo, FullpageLoading, InfoBanners } from "@/components"
@@ -93,7 +93,7 @@ const SearchPage: React.FC = () => {
9393
<IonTitle>Search</IonTitle>
9494
<IonButtons slot="end">
9595
<IonButton onClick={() => setOpenScanner(true)}>
96-
<IonIcon slot="icon-only" icon={barcodeOutline} />
96+
<IonIcon slot="icon-only" ios={barcodeOutline} md={barcodeSharp} />
9797
</IonButton>
9898
</IonButtons>
9999
</IonToolbar>

frontend/src/pages/Settings.tsx

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import {
1818
IonRow,
1919
IonToggle,
2020
IonButtons,
21+
IonSelectOption,
22+
IonSelect,
23+
getConfig,
2124
} from "@ionic/react"
2225
import { useHistory } from "react-router"
2326
import { useQueryClient } from "@tanstack/react-query"
@@ -26,6 +29,7 @@ import { IReleaseSet } from "@/api"
2629
import { useAuth, useSettings } from "@/hooks"
2730
import { formatBytes } from "@/utils"
2831
import { DonateButton, InfoBanners } from "@/components"
32+
import { DeviceMode } from "@/types"
2933

3034
const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({ hasUpdate, onUpdate }) => {
3135
const queryClient = useQueryClient()
@@ -35,7 +39,11 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
3539
const [newPassword, setNewPassword] = useState<string>(token ?? "")
3640
const [storageInfo, setStorageInfo] = useState<{ usage: string; quota: string } | undefined>()
3741
const [imageQuality, setImageQuality, clearImagequality] = useSettings<boolean>("ImagesAreHQ", false)
42+
const [deviceTheme, setDeviceTheme] = useSettings<DeviceMode>("DeviceTheme", "ios")
43+
const [restartAlert, setRestartAlert] = useState<boolean>(false)
3844
const appVersion = import.meta.env.VITE_VER ?? "Unknown"
45+
const ionConfig = getConfig()
46+
const currentMode = ionConfig?.get("mode") || "ios"
3947

4048
useEffect(() => {
4149
if ("storage" in navigator && "estimate" in navigator.storage) {
@@ -75,6 +83,8 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
7583
totalMissing: collectionMissing + wantedMissing,
7684
}
7785

86+
const lightMode = currentMode === "ios" ? "light" : undefined
87+
7888
return (
7989
<IonPage>
8090
<IonHeader>
@@ -90,14 +100,14 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
90100
</IonHeader>
91101
<IonContent className="ion-padding">
92102
<IonList inset={true}>
93-
<IonItem color="light">
103+
<IonItem color={lightMode}>
94104
<IonInput
95105
label="Username"
96106
value={newUsername}
97107
onIonChange={(e) => setNewUsername(`${e.target.value}`)}
98108
/>
99109
</IonItem>
100-
<IonItem color="light">
110+
<IonItem color={lightMode}>
101111
<IonInput
102112
type="password"
103113
label="Token"
@@ -114,7 +124,7 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
114124
token, or click Generate if you do not have one.
115125
</IonNote>
116126
<IonList inset={true}>
117-
<IonItem color="light">
127+
<IonItem color={lightMode}>
118128
<IonToggle checked={imageQuality} onIonChange={(e) => setImageQuality(e.detail.checked)}>
119129
Increase image quality
120130
</IonToggle>
@@ -124,7 +134,23 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
124134
If you have a large library, you may experience issues with this.
125135
</IonNote>
126136
<IonList inset={true}>
127-
<IonItem color="light">
137+
<IonItem color={lightMode}>
138+
<IonSelect
139+
label="Theme mode"
140+
interface="action-sheet"
141+
value={deviceTheme}
142+
onIonChange={(e) => {
143+
setDeviceTheme(e.detail.value)
144+
setRestartAlert(true)
145+
}}
146+
>
147+
<IonSelectOption value="ios">Apple</IonSelectOption>
148+
<IonSelectOption value="md">Android (beta)</IonSelectOption>
149+
</IonSelect>
150+
</IonItem>
151+
</IonList>
152+
<IonList inset={true}>
153+
<IonItem color={lightMode}>
128154
<IonLabel>App version</IonLabel>
129155
<IonLabel slot="end">
130156
{appVersion}
@@ -140,19 +166,19 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
140166
)}
141167
</IonLabel>
142168
</IonItem>
143-
<IonItem color="light">
169+
<IonItem color={lightMode}>
144170
<IonLabel>Storage used</IonLabel>
145171
<IonLabel slot="end">
146172
{storageInfo?.usage ?? "Unknown"} of {storageInfo?.quota ?? "Unknown"}
147173
</IonLabel>
148174
</IonItem>
149-
<IonItem color="light">
175+
<IonItem color={lightMode}>
150176
<IonLabel>Records stored</IonLabel>
151177
<IonLabel id="reccount-tooltip" slot="end">
152178
{inStorageInfo.totalCount}
153179
</IonLabel>
154180
</IonItem>
155-
<IonItem color="light">
181+
<IonItem color={lightMode}>
156182
<IonLabel>Records unsynced</IonLabel>
157183
<IonLabel id="missing-tooltip" slot="end">
158184
{inStorageInfo.totalMissing}
@@ -202,6 +228,13 @@ const SettingsPage: React.FC<{ hasUpdate: boolean; onUpdate: () => void }> = ({
202228
},
203229
]}
204230
/>
231+
<IonAlert
232+
isOpen={restartAlert}
233+
onDidDismiss={() => setRestartAlert(false)}
234+
header="Reload required"
235+
message="For these changes to take effect, you will need to reload the app."
236+
buttons={["OK"]}
237+
/>
205238
<IonNote color="medium" class="ion-margin-horizontal" style={{ display: "block", textAlign: "center" }}>
206239
Made by <a href="https://subo.dev">soup-bowl</a> and{" "}
207240
<a href="https://github.com/soup-bowl/Localib">open source</a>.

frontend/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export type IStatDisplay = {
1111
value?: number
1212
label: string
1313
}
14+
15+
export type DeviceMode = "ios" | "md"

frontend/src/utils/iconUtils.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { DeviceMode } from "@/types"
2+
import {
3+
filterOutline,
4+
filterSharp,
5+
pricetagOutline,
6+
pricetagSharp,
7+
personOutline,
8+
personSharp,
9+
timeOutline,
10+
timeSharp,
11+
gridOutline,
12+
gridSharp,
13+
listOutline,
14+
listSharp,
15+
} from "ionicons/icons"
16+
17+
export const getFilterIcon = (filter: string, platform: DeviceMode = "ios") => {
18+
switch (filter) {
19+
case "label":
20+
return platform === "ios" ? pricetagOutline : pricetagSharp
21+
case "artist":
22+
return platform === "ios" ? personOutline : personSharp
23+
case "release":
24+
return platform === "ios" ? timeOutline : timeSharp
25+
case "none":
26+
default:
27+
return platform === "ios" ? filterOutline : filterSharp
28+
}
29+
}
30+
31+
export const getLayoutIcon = (item: string, platform: DeviceMode = "ios") => {
32+
switch (item) {
33+
case "list":
34+
return platform === "ios" ? listOutline : listSharp
35+
case "grid":
36+
default:
37+
return platform === "ios" ? gridOutline : gridSharp
38+
}
39+
}

frontend/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { splitRecordsByYear, splitRecordsByLabel, splitRecordsByArtist, masterSort } from "./collectionSort"
2+
export { getFilterIcon, getLayoutIcon } from "./iconUtils"
23
export { formatBytes, formatCurrency, isNullOrBlank } from "./stringUtils"

0 commit comments

Comments
 (0)