A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Closes #192 - Add ability to migrate between data-storage methods

+166 -72
+1
CHANGELOG.md
··· 5 5 - **Adds album-covers view** (switch to and from list view with icon in nav bar) 6 6 - Added new background images and replaced two older ones 7 7 - Adds a keyboard shortcut `L` to quickly select a playlist 8 + - Adds the ability to migrate between data-storage methods 8 9 - Dark mode for the about page 9 10 - Fixes issue with shuffle algorithm 10 11 - Fixes playback issues (eg. clicking same track multiple times)
+1
src/Applications/Brain.elm
··· 74 74 , hypaethralStorage = [] 75 75 , hypaethralUserData = User.emptyHypaethralData 76 76 , legacyMode = False 77 + , migratingData = False 77 78 , origin = "ORIGIN_UNKNOWN" 78 79 , performingSignIn = False 79 80 , processingStatus = Processing.NotProcessing
+1
src/Applications/Brain/Types.elm
··· 31 31 , hypaethralStorage : List HypaethralBit 32 32 , hypaethralUserData : User.HypaethralData 33 33 , legacyMode : Bool 34 + , migratingData : Bool 34 35 , origin : String 35 36 , performingSignIn : Bool 36 37 , processingStatus : Processing.Status
+40 -17
src/Applications/Brain/User/State.elm
··· 163 163 -- and retrieve data. 164 164 let 165 165 decoder = 166 - Decode.map2 167 - Tuple.pair 166 + Decode.map3 167 + (\a b c -> ( a, Maybe.withDefault False b, c )) 168 168 (Decode.field "method" <| Decode.map methodFromString Decode.string) 169 + (Decode.field "migratingData" <| Decode.maybe Decode.bool) 169 170 (Decode.field "passphrase" <| Decode.maybe Decode.string) 170 171 in 171 172 case Decode.decodeValue decoder json of 172 - Ok ( maybeMethod, Just passphrase ) -> 173 + Ok ( maybeMethod, migratingData, Just passphrase ) -> 173 174 fabricateSecretKey 174 175 passphrase 175 - { model | authMethod = maybeMethod, performingSignIn = True } 176 + { model 177 + | authMethod = maybeMethod 178 + , migratingData = migratingData 179 + , performingSignIn = True 180 + } 176 181 177 - Ok ( maybeMethod, Nothing ) -> 178 - retrieveAllHypaethralData 179 - { model | authMethod = maybeMethod, performingSignIn = True } 182 + Ok ( maybeMethod, migratingData, Nothing ) -> 183 + (if migratingData then 184 + hypaethralDataRetrieved Json.null 185 + 186 + else 187 + retrieveAllHypaethralData 188 + ) 189 + { model 190 + | authMethod = maybeMethod 191 + , migratingData = migratingData 192 + , performingSignIn = True 193 + } 180 194 181 195 _ -> 182 196 Return.singleton model ··· 687 701 secretKeyFabricated : Manager 688 702 secretKeyFabricated model = 689 703 if model.performingSignIn then 690 - retrieveAllHypaethralData model 704 + if model.migratingData then 705 + hypaethralDataRetrieved Json.null model 706 + 707 + else 708 + retrieveAllHypaethralData model 691 709 692 710 else 693 711 saveAllHypaethralData model ··· 716 734 terminate t model = 717 735 case t of 718 736 Authenticated method encodedData -> 719 - let 720 - decodedData = 721 - encodedData 722 - |> User.decodeHypaethralData 723 - |> Result.withDefault model.hypaethralUserData 724 - in 725 - model 726 - |> sendHypaethralDataToUI encodedData decodedData 727 - |> andThen (Common.giveUI Alien.AuthMethod <| encodeMethod method) 737 + { model | migratingData = False } 738 + |> (if model.migratingData then 739 + Return.singleton 740 + 741 + else 742 + encodedData 743 + |> User.decodeHypaethralData 744 + |> Result.withDefault model.hypaethralUserData 745 + |> sendHypaethralDataToUI encodedData 746 + ) 747 + |> (encodeMethod method 748 + |> Common.giveUI Alien.AuthMethod 749 + |> andThen 750 + ) 728 751 729 752 NotAuthenticated -> 730 753 model
+5
src/Applications/UI.elm
··· 54 54 import UI.Tracks.State as Tracks 55 55 import UI.Tracks.Types as Tracks 56 56 import UI.Types exposing (..) 57 + import UI.User.State as User 57 58 import UI.User.State.Export as User 58 59 import UI.User.State.Import as User 59 60 import UI.View exposing (view) ··· 105 106 , isTouchDevice = False 106 107 , isUpgrading = flags.upgrade 107 108 , lastFm = LastFm.initialModel 109 + , migratingData = False 108 110 , navKey = key 109 111 , page = page 110 112 , pressedKeys = [] ··· 465 467 466 468 LoadHypaethralUserData a -> 467 469 User.loadHypaethralUserData a 470 + 471 + MigrateHypaethralUserData -> 472 + User.migrateHypaethralUserData 468 473 469 474 RequestImport -> 470 475 User.requestImport
+55 -33
src/Applications/UI/Authentication/State.elm
··· 31 31 import UI.Ports as Ports 32 32 import UI.Sources.State as Sources 33 33 import UI.Types as UI exposing (..) 34 + import UI.User.State.Import as User 34 35 import Url exposing (Protocol(..), Url) 35 36 import Url.Ext as Url 36 37 import User.Layer exposing (..) ··· 336 337 337 338 338 339 signedIn : Json.Value -> Manager 339 - signedIn json = 340 + signedIn json model = 340 341 -- 🧠 told me which auth method we're using, 341 342 -- so we can tell the user in the UI. 342 343 case decodeMethod json of 343 344 Just method -> 344 - replaceState (Authenticated method) 345 + model 346 + |> replaceState 347 + (Authenticated method) 348 + |> andThen 349 + (\m -> 350 + if m.migratingData then 351 + "Migrated data successfully" 352 + |> Notifications.success 353 + |> showNotificationWithModel { m | migratingData = False } 354 + |> User.saveAllHypaethralData 355 + 356 + else 357 + Return.singleton m 358 + ) 345 359 346 360 Nothing -> 347 - Return.singleton 361 + Return.singleton model 348 362 349 363 350 364 signIn : Method -> Manager 351 365 signIn method model = 352 366 [ ( "method", encodeMethod method ) 367 + , ( "migratingData", Json.Encode.bool model.migratingData ) 353 368 , ( "passphrase", Json.Encode.null ) 354 369 ] 355 370 |> Json.Encode.object ··· 369 384 370 385 else 371 386 [ ( "method", encodeMethod method ) 387 + , ( "migratingData", Json.Encode.bool model.migratingData ) 372 388 , ( "passphrase", Json.Encode.string <| hashPassphrase passphrase ) 373 389 ] 374 390 |> Json.Encode.object ··· 381 397 382 398 signOut : Manager 383 399 signOut model = 384 - { model 385 - | authentication = Authentication.Unauthenticated 386 - , playlists = [] 387 - , playlistToActivate = Nothing 400 + if model.migratingData then 401 + return 402 + { model | authentication = Authentication.Unauthenticated } 403 + (Ports.toBrain <| Alien.trigger Alien.SignOut) 388 404 389 - -- Queue 390 - -------- 391 - , dontPlay = [] 392 - , nowPlaying = Nothing 393 - , playedPreviously = [] 394 - , playingNext = [] 395 - , selectedQueueItem = Nothing 405 + else 406 + { model 407 + | authentication = Authentication.Unauthenticated 408 + , playlists = [] 409 + , playlistToActivate = Nothing 410 + 411 + -- Queue 412 + -------- 413 + , dontPlay = [] 414 + , nowPlaying = Nothing 415 + , playedPreviously = [] 416 + , playingNext = [] 417 + , selectedQueueItem = Nothing 396 418 397 - -- 398 - , repeat = False 399 - , shuffle = False 419 + -- 420 + , repeat = False 421 + , shuffle = False 400 422 401 - -- Sources 402 - ---------- 403 - , processingContext = [] 404 - , sources = [] 423 + -- Sources 424 + ---------- 425 + , processingContext = [] 426 + , sources = [] 405 427 406 - -- Tracks 407 - --------- 408 - , favourites = [] 409 - , hideDuplicates = False 410 - , searchResults = Nothing 411 - , tracks = Tracks.emptyCollection 412 - } 413 - |> Backdrop.setDefault 414 - |> Return.andThen Sources.stopProcessing 415 - |> Return.command (Ports.toBrain <| Alien.trigger Alien.SignOut) 416 - |> Return.command (Ports.activeQueueItemChanged Nothing) 417 - |> Return.command (Nav.pushUrl model.navKey "#/") 428 + -- Tracks 429 + --------- 430 + , favourites = [] 431 + , hideDuplicates = False 432 + , searchResults = Nothing 433 + , tracks = Tracks.emptyCollection 434 + } 435 + |> Backdrop.setDefault 436 + |> Return.andThen Sources.stopProcessing 437 + |> Return.command (Ports.toBrain <| Alien.trigger Alien.SignOut) 438 + |> Return.command (Ports.activeQueueItemChanged Nothing) 439 + |> Return.command (Nav.pushUrl model.navKey "#/") 418 440 419 441 420 442 startFlow : Manager
+47 -22
src/Applications/UI/Settings/ImportExport.elm
··· 46 46 |> raw 47 47 |> UI.Kit.intro 48 48 49 - -- Import 50 - --------- 51 - , chunk [ C.mb_2, C.mt_8 ] [ UI.Kit.label [] "Import" ] 52 - , UI.Kit.buttonWithColor 53 - UI.Kit.Gray 54 - Normal 55 - RequestImport 56 - (text "Choose file") 57 - , case userLayerMethod of 58 - Just Local -> 59 - otherImportOptions 49 + -- 50 + , chunk 51 + [ C.sm__flex, C.sm__minus_mt_6 ] 52 + [ -- Import 53 + --------- 54 + chunk 55 + [ C.flex_auto, C.pr_2 ] 56 + [ chunk [ C.mb_2, C.mt_8 ] [ UI.Kit.label [] "Import" ] 57 + , UI.Kit.buttonWithColor 58 + UI.Kit.Gray 59 + Normal 60 + RequestImport 61 + (text "Choose file") 62 + 63 + -- 64 + , case userLayerMethod of 65 + Just Local -> 66 + otherImportOptions 67 + 68 + Just (RemoteStorage _) -> 69 + otherImportOptions 60 70 61 - Just (RemoteStorage _) -> 62 - otherImportOptions 71 + _ -> 72 + nothing 73 + ] 63 74 64 - _ -> 65 - nothing 75 + -- Export 76 + --------- 77 + , chunk 78 + [ C.flex_auto, C.pl_2 ] 79 + [ chunk [ C.mb_2, C.mt_8 ] [ UI.Kit.label [] "Export" ] 80 + , UI.Kit.button 81 + Normal 82 + Export 83 + (text "Export data") 66 84 67 - -- Export 68 - --------- 69 - , chunk [ C.mb_2, C.mt_8 ] [ UI.Kit.label [] "Export" ] 70 - , UI.Kit.button 71 - Normal 72 - Export 73 - (text "Export data") 85 + -- 86 + , chunk 87 + [ C.italic, C.leading_normal, C.mt_5, C.text_xs ] 88 + [ text "Other options:" ] 89 + , chunk 90 + [ C.leading_normal, C.mt_2, C.text_sm ] 91 + [ inline [ C.mr_2 ] [ text "•" ] 92 + , UI.Kit.textButton 93 + { label = "Migrate to another storage" 94 + , onClick = MigrateHypaethralUserData 95 + } 96 + ] 97 + ] 98 + ] 74 99 ] 75 100 ] 76 101
+2
src/Applications/UI/Types.elm
··· 70 70 , isTouchDevice : Bool 71 71 , isUpgrading : Bool 72 72 , lastFm : LastFm.Model 73 + , migratingData : Bool 73 74 , navKey : Nav.Key 74 75 , page : Page 75 76 , pressedKeys : List Keyboard.Key ··· 279 280 | InsertDemo 280 281 | LoadEnclosedUserData Json.Decode.Value 281 282 | LoadHypaethralUserData Json.Decode.Value 283 + | MigrateHypaethralUserData 282 284 | RequestImport 283 285 | SaveEnclosedUserData 284 286 -----------------------------------------
+14
src/Applications/UI/User/State.elm
··· 1 + module UI.User.State exposing (..) 2 + 3 + import Return exposing (return) 4 + import UI.Authentication.State as Authentication 5 + import UI.Types as UI exposing (..) 6 + 7 + 8 + 9 + -- 🔱 10 + 11 + 12 + migrateHypaethralUserData : Manager 13 + migrateHypaethralUserData model = 14 + Authentication.signOut { model | migratingData = True }